diff --git a/backend/internal/modules/adminfinance/dashboard.go b/backend/internal/modules/adminfinance/dashboard.go index c696fa7..21c8bcd 100644 --- a/backend/internal/modules/adminfinance/dashboard.go +++ b/backend/internal/modules/adminfinance/dashboard.go @@ -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 { diff --git a/backend/internal/modules/adminfinance/detail.go b/backend/internal/modules/adminfinance/detail.go index 85e1b18..33cd9ed 100644 --- a/backend/internal/modules/adminfinance/detail.go +++ b/backend/internal/modules/adminfinance/detail.go @@ -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))). diff --git a/backend/internal/modules/adminfinance/dto.go b/backend/internal/modules/adminfinance/dto.go index 79053b0..694a8f8 100644 --- a/backend/internal/modules/adminfinance/dto.go +++ b/backend/internal/modules/adminfinance/dto.go @@ -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"` } diff --git a/backend/internal/modules/adminfinance/helper.go b/backend/internal/modules/adminfinance/helper.go index 77c3f78..8ac9bc8 100644 --- a/backend/internal/modules/adminfinance/helper.go +++ b/backend/internal/modules/adminfinance/helper.go @@ -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", diff --git a/backend/internal/modules/adminfinance/presenter.go b/backend/internal/modules/adminfinance/presenter.go index 889e01d..dbe3742 100644 --- a/backend/internal/modules/adminfinance/presenter.go +++ b/backend/internal/modules/adminfinance/presenter.go @@ -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, } } diff --git a/frontend/src/features/admin/api/adminFinance.ts b/frontend/src/features/admin/api/adminFinance.ts index 275c9bb..8a8282e 100644 --- a/frontend/src/features/admin/api/adminFinance.ts +++ b/frontend/src/features/admin/api/adminFinance.ts @@ -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 { diff --git a/frontend/src/features/admin/views/AdminFinanceDashboardView.vue b/frontend/src/features/admin/views/AdminFinanceDashboardView.vue index f2e59c7..8240e66 100644 --- a/frontend/src/features/admin/views/AdminFinanceDashboardView.vue +++ b/frontend/src/features/admin/views/AdminFinanceDashboardView.vue @@ -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' : '' +}