修复平台代管财务差异

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
@@ -80,9 +80,28 @@ func (r *Repository) summary(ctx context.Context, query DashboardQuery) (*Financ
var settlement settlementSummaryRow
if err := db.Table("rental_orders AS ro").
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(COALESCE(w.owner_wallet_income_amount_cent, 0)), 0) AS owner_wallet_income_amount_cent,
COUNT(ro.id) AS settled_order_count`).
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'
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`).
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)).
Where("ro.settled_at >= ? AND ro.settled_at <= ?", query.StartDate, query.EndDate).
@@ -96,7 +115,8 @@ func (r *Repository) summary(ctx context.Context, query DashboardQuery) (*Financ
StartDate: query.StartDate,
EndDate: query.EndDate,
})).
Where("finance_status <> ?", "normal").
Where("finance_status <> ?", financeStatusNormal).
Where("finance_status <> ?", financeStatusOfflineSettlementPending).
Count(&exceptionCount).Error; err != nil {
return nil, err
}
@@ -114,23 +134,27 @@ func (r *Repository) summary(ctx context.Context, query DashboardQuery) (*Financ
}
return &FinanceSummaryDTO{
TotalFlowAmountCent: payment.TotalFlowAmountCent,
TotalRefundAmountCent: payment.TotalRefundAmountCent,
PendingRefundAmountCent: payment.PendingRefundAmountCent,
ChannelNetAmountCent: payment.TotalFlowAmountCent - payment.TotalRefundAmountCent,
PlatformIncomeAmountCent: settlement.PlatformIncomeAmountCent,
OwnerShouldIncomeAmountCent: settlement.OwnerShouldIncomeAmountCent,
OwnerWalletIncomeAmountCent: settlement.OwnerWalletIncomeAmountCent,
SettlementDiffAmountCent: settlement.OwnerShouldIncomeAmountCent - settlement.OwnerWalletIncomeAmountCent,
EstimatedIncomeAmountCent: estimated.EstimatedIncomeAmountCent,
SuccessfulPayCount: payment.SuccessfulPayCount,
SuccessfulRefundCount: payment.FullRefundCount + payment.PartialRefundCount,
FullRefundCount: payment.FullRefundCount,
PartialRefundCount: payment.PartialRefundCount,
PendingRefundCount: payment.PendingRefundCount,
SettledOrderCount: settlement.SettledOrderCount,
PendingSettleOrderCount: estimated.PendingSettleOrderCount,
FinancialExceptionCount: exceptionCount,
TotalFlowAmountCent: payment.TotalFlowAmountCent,
TotalRefundAmountCent: payment.TotalRefundAmountCent,
PendingRefundAmountCent: payment.PendingRefundAmountCent,
ChannelNetAmountCent: payment.TotalFlowAmountCent - payment.TotalRefundAmountCent,
PlatformIncomeAmountCent: settlement.PlatformIncomeAmountCent,
OwnerShouldIncomeAmountCent: settlement.OwnerShouldIncomeAmountCent,
OwnerWalletIncomeAmountCent: settlement.OwnerWalletIncomeAmountCent,
OwnerEffectiveSettlementAmountCent: settlement.OwnerEffectiveSettlementAmountCent,
OfflineSettlementAmountCent: settlement.OfflineSettlementAmountCent,
OfflineSettlementPendingAmountCent: settlement.OfflineSettlementPendingAmountCent,
SettlementDiffAmountCent: settlement.OwnerShouldIncomeAmountCent - settlement.OwnerEffectiveSettlementAmountCent,
EstimatedIncomeAmountCent: estimated.EstimatedIncomeAmountCent,
SuccessfulPayCount: payment.SuccessfulPayCount,
SuccessfulRefundCount: payment.FullRefundCount + payment.PartialRefundCount,
FullRefundCount: payment.FullRefundCount,
PartialRefundCount: payment.PartialRefundCount,
PendingRefundCount: payment.PendingRefundCount,
SettledOrderCount: settlement.SettledOrderCount,
PendingSettleOrderCount: estimated.PendingSettleOrderCount,
OfflineSettlementPendingCount: settlement.OfflineSettlementPendingCount,
FinancialExceptionCount: exceptionCount,
}, nil
}
@@ -162,10 +186,29 @@ func (r *Repository) dailyItems(ctx context.Context, query DashboardQuery) ([]Fi
settlements := make([]dailySettlementRow, 0)
if err := db.Table("rental_orders AS ro").
Select(`DATE(ro.settled_at) AS date,
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(COALESCE(w.owner_wallet_income_amount_cent, 0)), 0) AS owner_wallet_income_amount_cent,
COUNT(ro.id) AS settled_order_count`).
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(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`).
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)).
Where("ro.settled_at >= ? AND ro.settled_at <= ?", query.StartDate, query.EndDate).
@@ -214,8 +257,12 @@ func (r *Repository) dailyItems(ctx context.Context, query DashboardQuery) ([]Fi
item.PlatformIncomeAmountCent = row.PlatformIncomeAmountCent
item.OwnerShouldIncomeAmountCent = row.OwnerShouldIncomeAmountCent
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.OfflineSettlementPendingCount = row.OfflineSettlementPendingCount
itemsByDate[date] = item
}
for _, row := range estimates {
@@ -244,10 +291,14 @@ type paymentSummaryRow struct {
}
type settlementSummaryRow struct {
PlatformIncomeAmountCent int64
OwnerShouldIncomeAmountCent int64
OwnerWalletIncomeAmountCent int64
SettledOrderCount int64
PlatformIncomeAmountCent int64
OwnerShouldIncomeAmountCent int64
OwnerWalletIncomeAmountCent int64
OwnerEffectiveSettlementAmountCent int64
OfflineSettlementAmountCent int64
OfflineSettlementPendingAmountCent int64
OfflineSettlementPendingCount int64
SettledOrderCount int64
}
type estimatedIncomeRow struct {
@@ -267,11 +318,15 @@ type dailyPaymentRow struct {
}
type dailySettlementRow struct {
Date string
PlatformIncomeAmountCent int64
OwnerShouldIncomeAmountCent int64
OwnerWalletIncomeAmountCent int64
SettledOrderCount int64
Date string
PlatformIncomeAmountCent int64
OwnerShouldIncomeAmountCent int64
OwnerWalletIncomeAmountCent int64
OwnerEffectiveSettlementAmountCent int64
OfflineSettlementAmountCent int64
OfflineSettlementPendingAmountCent int64
OfflineSettlementPendingCount int64
SettledOrderCount int64
}
type dailyEstimatedRow struct {
+41 -22
View File
@@ -61,28 +61,47 @@ func (r *Repository) applyDetailFilters(db *gorm.DB, 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").
Select(`ro.id AS order_id, ro.order_no, ro.status AS order_status, ro.settlement_status, ro.refund_status,
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.rent_amount_cent AS order_rent_amount_cent, ro.deposit_amount_cent AS order_deposit_amount_cent,
COALESCE(oc.rent_amount_cent, 0) AS checkout_rent_amount_cent,
COALESCE(oc.renter_refund_amount_cent, 0) AS checkout_renter_refund_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(w.owner_wallet_income_amount_cent, 0) AS owner_wallet_income_amount_cent,
COALESCE(p.paid_amount_cent, 0) AS paid_amount_cent,
COALESCE(p.refunded_amount_cent, 0) AS refunded_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.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.owner_income_amount_cent, 0) - COALESCE(w.owner_wallet_income_amount_cent, 0) AS settlement_diff_amount_cent,
CASE
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 ABS(COALESCE(oc.owner_income_amount_cent, 0) - COALESCE(w.owner_wallet_income_amount_cent, 0)) >= ? THEN 'settlement_diff'
ELSE 'normal'
END AS finance_status,
ro.created_at, ro.settled_at`, settlementDiffThresholdCent).
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.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,
COALESCE(oc.rent_amount_cent, 0) AS checkout_rent_amount_cent,
COALESCE(oc.renter_refund_amount_cent, 0) AS checkout_renter_refund_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(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.refunded_amount_cent, 0) AS refunded_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.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) - 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 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
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 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'
END AS finance_status,
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 ou ON ou.id = ro.owner_id").
Joins("LEFT JOIN (?) AS oc ON oc.order_id = ro.id", acceptedCheckoutSubquery(r.db.WithContext(ctx))).
+75 -61
View File
@@ -35,42 +35,50 @@ type PickupSummaryDTO struct {
}
type FinanceSummaryDTO struct {
TotalFlowAmountCent int64 `json:"total_flow_amount_cent"`
TotalRefundAmountCent int64 `json:"total_refund_amount_cent"`
PendingRefundAmountCent int64 `json:"pending_refund_amount_cent"`
ChannelNetAmountCent int64 `json:"channel_net_amount_cent"`
PlatformIncomeAmountCent int64 `json:"platform_income_amount_cent"`
OwnerShouldIncomeAmountCent int64 `json:"owner_should_income_amount_cent"`
OwnerWalletIncomeAmountCent int64 `json:"owner_wallet_income_amount_cent"`
SettlementDiffAmountCent int64 `json:"settlement_diff_amount_cent"`
EstimatedIncomeAmountCent int64 `json:"estimated_income_amount_cent"`
SuccessfulPayCount int64 `json:"successful_pay_count"`
SuccessfulRefundCount int64 `json:"successful_refund_count"`
FullRefundCount int64 `json:"full_refund_count"`
PartialRefundCount int64 `json:"partial_refund_count"`
PendingRefundCount int64 `json:"pending_refund_count"`
SettledOrderCount int64 `json:"settled_order_count"`
PendingSettleOrderCount int64 `json:"pending_settle_order_count"`
FinancialExceptionCount int64 `json:"financial_exception_count"`
TotalFlowAmountCent int64 `json:"total_flow_amount_cent"`
TotalRefundAmountCent int64 `json:"total_refund_amount_cent"`
PendingRefundAmountCent int64 `json:"pending_refund_amount_cent"`
ChannelNetAmountCent int64 `json:"channel_net_amount_cent"`
PlatformIncomeAmountCent int64 `json:"platform_income_amount_cent"`
OwnerShouldIncomeAmountCent int64 `json:"owner_should_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"`
EstimatedIncomeAmountCent int64 `json:"estimated_income_amount_cent"`
SuccessfulPayCount int64 `json:"successful_pay_count"`
SuccessfulRefundCount int64 `json:"successful_refund_count"`
FullRefundCount int64 `json:"full_refund_count"`
PartialRefundCount int64 `json:"partial_refund_count"`
PendingRefundCount int64 `json:"pending_refund_count"`
SettledOrderCount int64 `json:"settled_order_count"`
PendingSettleOrderCount int64 `json:"pending_settle_order_count"`
OfflineSettlementPendingCount int64 `json:"offline_settlement_pending_count"`
FinancialExceptionCount int64 `json:"financial_exception_count"`
}
type FinanceDailyDTO struct {
Date string `json:"date"`
TotalFlowAmountCent int64 `json:"total_flow_amount_cent"`
TotalRefundAmountCent int64 `json:"total_refund_amount_cent"`
PendingRefundAmountCent int64 `json:"pending_refund_amount_cent"`
ChannelNetAmountCent int64 `json:"channel_net_amount_cent"`
PlatformIncomeAmountCent int64 `json:"platform_income_amount_cent"`
OwnerShouldIncomeAmountCent int64 `json:"owner_should_income_amount_cent"`
OwnerWalletIncomeAmountCent int64 `json:"owner_wallet_income_amount_cent"`
SettlementDiffAmountCent int64 `json:"settlement_diff_amount_cent"`
EstimatedIncomeAmountCent int64 `json:"estimated_income_amount_cent"`
SuccessfulPayCount int64 `json:"successful_pay_count"`
SuccessfulRefundCount int64 `json:"successful_refund_count"`
FullRefundCount int64 `json:"full_refund_count"`
PartialRefundCount int64 `json:"partial_refund_count"`
PendingRefundCount int64 `json:"pending_refund_count"`
SettledOrderCount int64 `json:"settled_order_count"`
Date string `json:"date"`
TotalFlowAmountCent int64 `json:"total_flow_amount_cent"`
TotalRefundAmountCent int64 `json:"total_refund_amount_cent"`
PendingRefundAmountCent int64 `json:"pending_refund_amount_cent"`
ChannelNetAmountCent int64 `json:"channel_net_amount_cent"`
PlatformIncomeAmountCent int64 `json:"platform_income_amount_cent"`
OwnerShouldIncomeAmountCent int64 `json:"owner_should_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"`
EstimatedIncomeAmountCent int64 `json:"estimated_income_amount_cent"`
SuccessfulPayCount int64 `json:"successful_pay_count"`
SuccessfulRefundCount int64 `json:"successful_refund_count"`
FullRefundCount int64 `json:"full_refund_count"`
PartialRefundCount int64 `json:"partial_refund_count"`
PendingRefundCount int64 `json:"pending_refund_count"`
SettledOrderCount int64 `json:"settled_order_count"`
OfflineSettlementPendingCount int64 `json:"offline_settlement_pending_count"`
}
type PaginatedResult struct {
@@ -81,32 +89,38 @@ type PaginatedResult struct {
}
type FinanceDetailDTO struct {
OrderID uint64 `json:"order_id"`
OrderNo string `json:"order_no"`
OrderStatus string `json:"order_status"`
SettlementStatus string `json:"settlement_status"`
RefundStatus string `json:"refund_status"`
RenterID uint64 `json:"renter_id"`
RenterPhone string `json:"renter_phone"`
RenterNickname string `json:"renter_nickname"`
OwnerID uint64 `json:"owner_id"`
OwnerPhone string `json:"owner_phone"`
OwnerNickname string `json:"owner_nickname"`
OrderRentAmountCent int64 `json:"order_rent_amount_cent"`
OrderDepositAmountCent int64 `json:"order_deposit_amount_cent"`
CheckoutRentAmountCent int64 `json:"checkout_rent_amount_cent"`
CheckoutRenterRefundCent int64 `json:"checkout_renter_refund_cent"`
CheckoutOwnerIncomeCent int64 `json:"checkout_owner_income_cent"`
CheckoutPlatformFeeCent int64 `json:"checkout_platform_fee_cent"`
OwnerWalletIncomeAmountCent int64 `json:"owner_wallet_income_amount_cent"`
PaidAmountCent int64 `json:"paid_amount_cent"`
RefundedAmountCent int64 `json:"refunded_amount_cent"`
RefundingAmountCent int64 `json:"refunding_amount_cent"`
FailedRefundAmountCent int64 `json:"failed_refund_amount_cent"`
ChannelNetAmountCent int64 `json:"channel_net_amount_cent"`
PlatformNetAmountCent int64 `json:"platform_net_amount_cent"`
SettlementDiffAmountCent int64 `json:"settlement_diff_amount_cent"`
FinanceStatus string `json:"finance_status"`
CreatedAt time.Time `json:"created_at"`
SettledAt *time.Time `json:"settled_at,omitempty"`
OrderID uint64 `json:"order_id"`
OrderNo string `json:"order_no"`
OrderStatus string `json:"order_status"`
SettlementStatus string `json:"settlement_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"`
RenterPhone string `json:"renter_phone"`
RenterNickname string `json:"renter_nickname"`
OwnerID uint64 `json:"owner_id"`
OwnerPhone string `json:"owner_phone"`
OwnerNickname string `json:"owner_nickname"`
OrderRentAmountCent int64 `json:"order_rent_amount_cent"`
OrderDepositAmountCent int64 `json:"order_deposit_amount_cent"`
CheckoutRentAmountCent int64 `json:"checkout_rent_amount_cent"`
CheckoutRenterRefundCent int64 `json:"checkout_renter_refund_cent"`
CheckoutOwnerIncomeCent int64 `json:"checkout_owner_income_cent"`
CheckoutPlatformFeeCent int64 `json:"checkout_platform_fee_cent"`
OwnerWalletIncomeAmountCent int64 `json:"owner_wallet_income_amount_cent"`
OwnerEffectiveSettlementAmountCent int64 `json:"owner_effective_settlement_amount_cent"`
PaidAmountCent int64 `json:"paid_amount_cent"`
RefundedAmountCent int64 `json:"refunded_amount_cent"`
RefundingAmountCent int64 `json:"refunding_amount_cent"`
FailedRefundAmountCent int64 `json:"failed_refund_amount_cent"`
ChannelNetAmountCent int64 `json:"channel_net_amount_cent"`
PlatformNetAmountCent int64 `json:"platform_net_amount_cent"`
SettlementDiffAmountCent int64 `json:"settlement_diff_amount_cent"`
FinanceStatus string `json:"finance_status"`
CreatedAt time.Time `json:"created_at"`
SettledAt *time.Time `json:"settled_at,omitempty"`
OfflineSettledAt *time.Time `json:"offline_settled_at,omitempty"`
}
@@ -12,6 +12,14 @@ import (
// SQL、presenter、前端展示统一引用此口径,避免多处硬编码不一致。
const settlementDiffThresholdCent = 5
const (
financeStatusNormal = "normal"
financeStatusRefundPending = "refund_pending"
financeStatusRefundFailed = "refund_failed"
financeStatusSettlementDiff = "settlement_diff"
financeStatusOfflineSettlementPending = "offline_settlement_pending"
)
func refundBizTypes() []string {
return []string{
"cancel_refund",
@@ -5,73 +5,85 @@ import (
)
type financeDetailRow struct {
OrderID uint64
OrderNo string
OrderStatus string
SettlementStatus string
RefundStatus string
RenterID uint64
RenterPhone string
RenterNickname string
OwnerID uint64
OwnerPhone string
OwnerNickname string
OrderRentAmountCent int64
OrderDepositAmountCent int64
CheckoutRentAmountCent int64
CheckoutRenterRefundCent int64
CheckoutOwnerIncomeCent int64
CheckoutPlatformFeeCent int64
OwnerWalletIncomeAmountCent int64
PaidAmountCent int64
RefundedAmountCent int64
RefundingAmountCent int64
FailedRefundAmountCent int64
ChannelNetAmountCent int64
PlatformNetAmountCent int64
SettlementDiffAmountCent int64
FinanceStatus string
CreatedAt time.Time
SettledAt *time.Time
OrderID uint64
OrderNo string
OrderStatus string
SettlementStatus string
RefundStatus string
HandoffMode string
SettlementMode string
OfflineSettlementStatus string
OfflineSettlementAmountCent int64
RenterID uint64
RenterPhone string
RenterNickname string
OwnerID uint64
OwnerPhone string
OwnerNickname string
OrderRentAmountCent int64
OrderDepositAmountCent int64
CheckoutRentAmountCent int64
CheckoutRenterRefundCent int64
CheckoutOwnerIncomeCent int64
CheckoutPlatformFeeCent int64
OwnerWalletIncomeAmountCent int64
OwnerEffectiveSettlementAmountCent int64
PaidAmountCent int64
RefundedAmountCent int64
RefundingAmountCent int64
FailedRefundAmountCent int64
ChannelNetAmountCent int64
PlatformNetAmountCent int64
SettlementDiffAmountCent int64
FinanceStatus string
CreatedAt time.Time
SettledAt *time.Time
OfflineSettledAt *time.Time
}
func (r financeDetailRow) toDTO() FinanceDetailDTO {
diff := r.SettlementDiffAmountCent
status := r.FinanceStatus
if status == "" {
status = "normal"
status = financeStatusNormal
}
if absCent(diff) < settlementDiffThresholdCent && status == "settlement_diff" {
status = "normal"
if absCent(diff) < settlementDiffThresholdCent && status == financeStatusSettlementDiff {
status = financeStatusNormal
}
return FinanceDetailDTO{
OrderID: r.OrderID,
OrderNo: r.OrderNo,
OrderStatus: r.OrderStatus,
SettlementStatus: r.SettlementStatus,
RefundStatus: r.RefundStatus,
RenterID: r.RenterID,
RenterPhone: r.RenterPhone,
RenterNickname: r.RenterNickname,
OwnerID: r.OwnerID,
OwnerPhone: r.OwnerPhone,
OwnerNickname: r.OwnerNickname,
OrderRentAmountCent: r.OrderRentAmountCent,
OrderDepositAmountCent: r.OrderDepositAmountCent,
CheckoutRentAmountCent: r.CheckoutRentAmountCent,
CheckoutRenterRefundCent: r.CheckoutRenterRefundCent,
CheckoutOwnerIncomeCent: r.CheckoutOwnerIncomeCent,
CheckoutPlatformFeeCent: r.CheckoutPlatformFeeCent,
OwnerWalletIncomeAmountCent: r.OwnerWalletIncomeAmountCent,
PaidAmountCent: r.PaidAmountCent,
RefundedAmountCent: r.RefundedAmountCent,
RefundingAmountCent: r.RefundingAmountCent,
FailedRefundAmountCent: r.FailedRefundAmountCent,
ChannelNetAmountCent: r.ChannelNetAmountCent,
PlatformNetAmountCent: r.PlatformNetAmountCent,
SettlementDiffAmountCent: diff,
FinanceStatus: status,
CreatedAt: r.CreatedAt,
SettledAt: r.SettledAt,
OrderID: r.OrderID,
OrderNo: r.OrderNo,
OrderStatus: r.OrderStatus,
SettlementStatus: r.SettlementStatus,
RefundStatus: r.RefundStatus,
HandoffMode: r.HandoffMode,
SettlementMode: r.SettlementMode,
OfflineSettlementStatus: r.OfflineSettlementStatus,
OfflineSettlementAmountCent: r.OfflineSettlementAmountCent,
RenterID: r.RenterID,
RenterPhone: r.RenterPhone,
RenterNickname: r.RenterNickname,
OwnerID: r.OwnerID,
OwnerPhone: r.OwnerPhone,
OwnerNickname: r.OwnerNickname,
OrderRentAmountCent: r.OrderRentAmountCent,
OrderDepositAmountCent: r.OrderDepositAmountCent,
CheckoutRentAmountCent: r.CheckoutRentAmountCent,
CheckoutRenterRefundCent: r.CheckoutRenterRefundCent,
CheckoutOwnerIncomeCent: r.CheckoutOwnerIncomeCent,
CheckoutPlatformFeeCent: r.CheckoutPlatformFeeCent,
OwnerWalletIncomeAmountCent: r.OwnerWalletIncomeAmountCent,
OwnerEffectiveSettlementAmountCent: r.OwnerEffectiveSettlementAmountCent,
PaidAmountCent: r.PaidAmountCent,
RefundedAmountCent: r.RefundedAmountCent,
RefundingAmountCent: r.RefundingAmountCent,
FailedRefundAmountCent: r.FailedRefundAmountCent,
ChannelNetAmountCent: r.ChannelNetAmountCent,
PlatformNetAmountCent: r.PlatformNetAmountCent,
SettlementDiffAmountCent: diff,
FinanceStatus: status,
CreatedAt: r.CreatedAt,
SettledAt: r.SettledAt,
OfflineSettledAt: r.OfflineSettledAt,
}
}
@@ -1,7 +1,7 @@
import { apiClient } from '@/shared/api/client'
import type { ApiResponse, PaginatedResult } from '@/shared/types/types'
// 结算差异判定阈值(分)。号主应得与实际入账差额绝对值达到此值即标红。
// 结算差异判定阈值(分)。号主应得与钱包入账/线下结算差额绝对值达到此值即标红。
// 与后端 settlementDiffThresholdCent 保持一致,改口径时两端同步。
export const SETTLEMENT_DIFF_THRESHOLD_CENT = 5
@@ -13,6 +13,9 @@ export interface FinanceSummary {
platform_income_amount_cent: number
owner_should_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
estimated_income_amount_cent: number
successful_pay_count: number
@@ -22,6 +25,7 @@ export interface FinanceSummary {
pending_refund_count: number
settled_order_count: number
pending_settle_order_count: number
offline_settlement_pending_count: number
financial_exception_count: number
}
@@ -34,6 +38,9 @@ export interface FinanceDailyItem {
platform_income_amount_cent: number
owner_should_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
estimated_income_amount_cent: number
successful_pay_count: number
@@ -42,6 +49,7 @@ export interface FinanceDailyItem {
partial_refund_count: number
pending_refund_count: number
settled_order_count: number
offline_settlement_pending_count: number
}
export interface FinancePickupSummary {
@@ -64,6 +72,10 @@ export interface FinanceDetail {
order_status: string
settlement_status: string
refund_status: string
handoff_mode: string
settlement_mode: string
offline_settlement_status: string
offline_settlement_amount_cent: number
renter_id: number
renter_phone: string
renter_nickname: string
@@ -77,6 +89,7 @@ export interface FinanceDetail {
checkout_owner_income_cent: number
checkout_platform_fee_cent: number
owner_wallet_income_amount_cent: number
owner_effective_settlement_amount_cent: number
paid_amount_cent: number
refunded_amount_cent: number
refunding_amount_cent: number
@@ -87,6 +100,7 @@ export interface FinanceDetail {
finance_status: string
created_at: string
settled_at?: string
offline_settled_at?: string
}
export interface FinanceDateQuery {
@@ -54,6 +54,10 @@ function rowDiffClass(row: FinanceDailyItem) {
? 'amount-danger'
: ''
}
function rowOfflinePendingClass(row: FinanceDailyItem) {
return Number(row.offline_settlement_pending_amount_cent || 0) > 0 ? 'amount-warning' : ''
}
</script>
<template>
@@ -62,7 +66,7 @@ function rowDiffClass(row: FinanceDailyItem) {
<div class="page-header">
<p class="eyebrow">Finance Dashboard</p>
<h1>财务仪表盘</h1>
<p>按天查看第三方收款退款平台收入号主入账和结算差异</p>
<p>按天查看第三方收款退款平台收入号主钱包入账/线下结算和结算差异</p>
</div>
<div class="toolbar-actions">
<el-date-picker
@@ -114,9 +118,14 @@ function rowDiffClass(row: FinanceDailyItem) {
<small>结账单口径</small>
</div>
<div class="metric-card">
<span>号主实际入账</span>
<strong>{{ moneyCent(dashboard.summary.owner_wallet_income_amount_cent) }}</strong>
<small>钱包流水口径</small>
<span>号主结算金额</span>
<strong>{{ moneyCent(dashboard.summary.owner_effective_settlement_amount_cent) }}</strong>
<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 class="metric-card">
<span>退款中</span>
@@ -174,8 +183,17 @@ function rowDiffClass(row: FinanceDailyItem) {
<el-table-column label="号主应得" width="130">
<template #default="{ row }">{{ moneyCent(row.owner_should_income_amount_cent) }}</template>
</el-table-column>
<el-table-column label="号主入账" width="130">
<template #default="{ row }">{{ moneyCent(row.owner_wallet_income_amount_cent) }}</template>
<el-table-column label="结算金额" width="130">
<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 label="结算差异" width="130">
<template #default="{ row }">
@@ -211,6 +229,11 @@ function rowDiffClass(row: FinanceDailyItem) {
font-weight: 700;
}
.amount-warning {
color: #d97706;
font-weight: 700;
}
.pickup-grid {
margin-top: 12px;
}
@@ -71,6 +71,7 @@ function financeStatusLabel(status: string) {
refund_pending: '退款中',
refund_failed: '退款失败',
settlement_diff: '结算差异',
offline_settlement_pending: '待线下结算',
}
return map[status] || status
}
@@ -78,7 +79,7 @@ function financeStatusLabel(status: string) {
function financeStatusType(status: string) {
if (status === 'normal') return 'success'
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'
}
@@ -91,6 +92,23 @@ function settlementStatusLabel(status: string) {
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) {
if (!value) return '-'
const text = formatDateTime(value)
@@ -114,7 +132,7 @@ function defaultEndDate() {
<div class="page-header">
<p class="eyebrow">Finance Details</p>
<h1>财务明细</h1>
<p>按订单核对收款退款平台收入号主应得与钱包实际入账</p>
<p>按订单核对收款退款平台收入号主应得与钱包入账/线下结算</p>
</div>
<div class="toolbar-actions">
<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>
@@ -206,7 +224,13 @@ function defaultEndDate() {
<div class="status-stack">
<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)">
{{ financeStatusLabel(row.finance_status) }}
</el-tag>
@@ -232,7 +256,7 @@ function defaultEndDate() {
moneyCent(row.checkout_owner_income_cent)
}}</span>
<span class="amount-text amount-cell">{{
moneyCent(row.owner_wallet_income_amount_cent)
moneyCent(row.owner_effective_settlement_amount_cent)
}}</span>
<span
class="amount-text amount-cell"