diff --git a/backend/internal/model/pickup.go b/backend/internal/model/pickup.go index 2446076..5e4f16e 100644 --- a/backend/internal/model/pickup.go +++ b/backend/internal/model/pickup.go @@ -7,7 +7,7 @@ import ( ) // AdminPickup 管理员线下提号记录。 -// 独立于 rental_orders,不进入正常订单状态机与财务统计口径。 +// 独立于 rental_orders 和正常订单状态机;完成后进入线下提号财务统计。 // 站内上传完成时入卖家钱包;平台代管完成后进入线下结算。 type AdminPickup struct { ID uint64 `gorm:"primaryKey" json:"id"` diff --git a/backend/internal/modules/adminfinance/dashboard.go b/backend/internal/modules/adminfinance/dashboard.go index 0bf89d4..f868ff7 100644 --- a/backend/internal/modules/adminfinance/dashboard.go +++ b/backend/internal/modules/adminfinance/dashboard.go @@ -125,11 +125,33 @@ func (r *Repository) pickupSummary(ctx context.Context, query DashboardQuery) (* if err := db.Table("admin_pickups").Where("status = ?", "picking_up").Count(&inProgress).Error; err != nil { return nil, err } + var pending struct { + AmountCent int64 + Count int64 + } + if err := db.Table("admin_pickups"). + Select(`COALESCE(SUM(settle_amount_cent), 0) AS amount_cent, COUNT(id) AS count`). + Where("settlement_mode = ? AND offline_settlement_status = ? AND settle_amount_cent > 0", "platform_managed", "pending"). + Scan(&pending).Error; err != nil { + return nil, err + } + var pendingAdjustments struct { + AmountCent int64 + Count int64 + } + if err := db.Table("admin_pickup_financial_adjustments"). + Select(`COALESCE(SUM(settle_delta_cent), 0) AS amount_cent, COUNT(id) AS count`). + Where("settlement_mode = ? AND status = ? AND settle_delta_cent > 0", "platform_managed", "pending"). + Scan(&pendingAdjustments).Error; err != nil { + return nil, err + } return &PickupSummaryDTO{ SettledAmountCent: row.SettledAmountCent + adjustments.SettledAmountCent, ProfitAmountCent: row.ProfitAmountCent + adjustments.ProfitAmountCent, CompletedCount: row.CompletedCount, InProgressCount: inProgress, + PendingAmountCent: pending.AmountCent + pendingAdjustments.AmountCent, + PendingCount: pending.Count + pendingAdjustments.Count, }, nil } @@ -250,7 +272,7 @@ func (r *Repository) summary(ctx context.Context, query DashboardQuery, pickup * OwnerWalletIncomeAmountCent: settlement.OwnerWalletIncomeAmountCent, OwnerEffectiveSettlementAmountCent: settlement.OwnerEffectiveSettlementAmountCent, OfflineSettlementAmountCent: settlement.OfflineSettlementAmountCent, - OfflineSettlementPendingAmountCent: settlement.OfflineSettlementPendingAmountCent, + OfflineSettlementPendingAmountCent: settlement.OfflineSettlementPendingAmountCent + pickup.PendingAmountCent, SettlementDiffAmountCent: settlement.OwnerShouldIncomeAmountCent - settlement.OwnerEffectiveSettlementAmountCent, EstimatedIncomeAmountCent: estimated.EstimatedIncomeAmountCent, SuccessfulPayCount: payment.SuccessfulPayCount, @@ -260,7 +282,7 @@ func (r *Repository) summary(ctx context.Context, query DashboardQuery, pickup * PendingRefundCount: payment.PendingRefundCount, SettledOrderCount: settlement.SettledOrderCount, PendingSettleOrderCount: estimated.PendingSettleOrderCount, - OfflineSettlementPendingCount: settlement.OfflineSettlementPendingCount, + OfflineSettlementPendingCount: settlement.OfflineSettlementPendingCount + pickup.PendingCount, FinancialExceptionCount: settlement.FinancialExceptionCount, }, nil } @@ -499,7 +521,7 @@ func (r *Repository) dailyPickupCancelled(ctx context.Context, query DashboardQu return rows, err } -// dailyPaidOrders 按实际付款日统计普通租赁订单,并将创建的线下提号视为已付款成交。 +// dailyPaidOrders 按实际付款日统计普通租赁订单,并将已完成的线下提号按完成日计入成交。 func (r *Repository) dailyPaidOrders(ctx context.Context, query DashboardQuery) ([]dailyPaidOrderRow, error) { items := make([]dailyPaidOrderRow, 0) err := r.db.WithContext(ctx).Table(`( @@ -508,10 +530,10 @@ func (r *Repository) dailyPaidOrders(ctx context.Context, query DashboardQuery) WHERE biz_type = 'order_pay' AND status = 'paid' AND paid_at >= ? AND paid_at <= ? GROUP BY DATE(paid_at) UNION ALL - SELECT DATE(created_at) AS date, COUNT(id) AS paid_order_count + SELECT DATE(completed_at) AS date, COUNT(id) AS paid_order_count FROM admin_pickups - WHERE created_at >= ? AND created_at <= ? - GROUP BY DATE(created_at) + WHERE status = 'completed' AND completed_at >= ? AND completed_at <= ? + GROUP BY DATE(completed_at) ) AS daily_paid_orders`, query.StartDate, query.EndDate, query.StartDate, query.EndDate). Select("date, SUM(paid_order_count) AS paid_order_count"). Group("date"). diff --git a/backend/internal/modules/adminfinance/dashboard_test.go b/backend/internal/modules/adminfinance/dashboard_test.go index 754b040..a0821c2 100644 --- a/backend/internal/modules/adminfinance/dashboard_test.go +++ b/backend/internal/modules/adminfinance/dashboard_test.go @@ -27,7 +27,9 @@ func TestDailyPaidOrdersUsePaidAtAndIncludeCreatedPickups(t *testing.T) { } if err := db.Exec(`CREATE TABLE admin_pickups ( id INTEGER PRIMARY KEY, - created_at DATETIME NOT NULL + status TEXT NOT NULL, + created_at DATETIME NOT NULL, + completed_at DATETIME NULL )`).Error; err != nil { t.Fatalf("创建提号表失败: %v", err) } @@ -49,12 +51,12 @@ func TestDailyPaidOrdersUsePaidAtAndIncludeCreatedPickups(t *testing.T) { } } for _, row := range [][]any{ - {1, day(2)}, - {2, day(2)}, - {3, day(3)}, - {4, day(1)}, + {1, "completed", day(2), day(2)}, + {2, "picking_up", day(2), nil}, + {3, "completed", day(3), day(3)}, + {4, "cancelled", day(1), nil}, } { - if err := db.Exec(`INSERT INTO admin_pickups (id, created_at) VALUES (?, ?)`, row...).Error; err != nil { + if err := db.Exec(`INSERT INTO admin_pickups (id, status, created_at, completed_at) VALUES (?, ?, ?, ?)`, row...).Error; err != nil { t.Fatalf("写入提号记录失败: %v", err) } } @@ -75,8 +77,8 @@ func TestDailyPaidOrdersUsePaidAtAndIncludeCreatedPickups(t *testing.T) { for _, row := range result { counts[row.Date] = row.PaidOrderCount } - if counts["2026-08-02"] != 3 || counts["2026-08-03"] != 2 { - t.Fatalf("每日已付款订单 = %#v, want 8月2日为3、8月3日为2", counts) + if counts["2026-08-02"] != 2 || counts["2026-08-03"] != 2 { + t.Fatalf("每日已付款订单 = %#v, want 8月2日为2、8月3日为2", counts) } } @@ -194,7 +196,9 @@ func TestPickupSummaryKeepsOriginalAndAdjustmentOnTheirOwnDates(t *testing.T) { status TEXT NOT NULL, settle_amount_cent INTEGER NOT NULL, profit_amount_cent INTEGER NOT NULL, - completed_at DATETIME NULL + completed_at DATETIME NULL, + settlement_mode TEXT NOT NULL DEFAULT 'owner_wallet', + offline_settlement_status TEXT NOT NULL DEFAULT 'none' )`).Error; err != nil { t.Fatalf("创建提号表失败: %v", err) } @@ -203,7 +207,9 @@ func TestPickupSummaryKeepsOriginalAndAdjustmentOnTheirOwnDates(t *testing.T) { pickup_id INTEGER NOT NULL, settle_delta_cent INTEGER NOT NULL, profit_delta_cent INTEGER NOT NULL, - created_at DATETIME NOT NULL + created_at DATETIME NOT NULL, + settlement_mode TEXT NOT NULL DEFAULT 'owner_wallet', + status TEXT NOT NULL DEFAULT 'settled' )`).Error; err != nil { t.Fatalf("创建提号调整表失败: %v", err) } @@ -267,11 +273,13 @@ func TestSummaryReturnsNormalOrderCountAndScopedExceptionCount(t *testing.T) { )`, `CREATE TABLE admin_pickups ( id INTEGER PRIMARY KEY, status TEXT NOT NULL, settle_amount_cent INTEGER NOT NULL, - profit_amount_cent INTEGER NOT NULL, completed_at DATETIME NULL + profit_amount_cent INTEGER NOT NULL, completed_at DATETIME NULL, + settlement_mode TEXT NOT NULL DEFAULT 'owner_wallet', offline_settlement_status TEXT NOT NULL DEFAULT 'none' )`, `CREATE TABLE admin_pickup_financial_adjustments ( id INTEGER PRIMARY KEY, pickup_id INTEGER NOT NULL, settle_delta_cent INTEGER NOT NULL, - profit_delta_cent INTEGER NOT NULL, created_at DATETIME NOT NULL + profit_delta_cent INTEGER NOT NULL, created_at DATETIME NOT NULL, + settlement_mode TEXT NOT NULL DEFAULT 'owner_wallet', status TEXT NOT NULL DEFAULT 'settled' )`, } { if err := db.Exec(statement).Error; err != nil { diff --git a/backend/internal/modules/adminfinance/disbursement.go b/backend/internal/modules/adminfinance/disbursement.go index df46323..1a133f4 100644 --- a/backend/internal/modules/adminfinance/disbursement.go +++ b/backend/internal/modules/adminfinance/disbursement.go @@ -143,8 +143,8 @@ func (r *Repository) disbursementSummary(ctx context.Context, query DashboardQue PendingPaymentCount: offlinePending.Count + pickupPending.Count + pickupAdjustmentPending.Count + withdrawal.PendingCount, OfflineSettlementPaidAmountCent: offlinePaid.AmountCent + pickupPaid.AmountCent + pickupAdjustmentPaid.AmountCent, OfflineSettlementPaidCount: offlinePaid.Count + pickupPaid.Count + pickupAdjustmentPaid.Count, - OfflineSettlementPendingAmountCent: offlinePending.AmountCent, - OfflineSettlementPendingCount: offlinePending.Count, + OfflineSettlementPendingAmountCent: offlinePending.AmountCent + pickupPending.AmountCent + pickupAdjustmentPending.AmountCent, + OfflineSettlementPendingCount: offlinePending.Count + pickupPending.Count + pickupAdjustmentPending.Count, WithdrawalAmountCent: withdrawal.PaidAmountCent, WithdrawalFeeAmountCent: withdrawal.PaidFeeAmountCent, WithdrawalPaidAmountCent: withdrawal.PaidActualAmountCent, diff --git a/backend/internal/modules/adminfinance/disbursement_list.go b/backend/internal/modules/adminfinance/disbursement_list.go index 8b390ed..f9276d3 100644 --- a/backend/internal/modules/adminfinance/disbursement_list.go +++ b/backend/internal/modules/adminfinance/disbursement_list.go @@ -95,6 +95,83 @@ func (r *Repository) disbursementListBaseQuery(ctx context.Context) *gorm.DB { Where("ro.offline_settlement_status IN ?", []string{"pending", "settled"}). Where("ro.offline_settlement_amount_cent > 0") + pickupManaged := db.Table("admin_pickups AS ap"). + Select(`'pickup' AS source_type, + ap.id AS source_id, + ap.pickup_no AS business_no, + 0 AS order_id, + 0 AS withdrawal_id, + ap.owner_id AS user_id, + '外部卖家' AS payee_name, + '' AS payee_phone, + '' AS uploader_name, + ap.source_channel, + '' AS account_type, + '' AS account_name, + '' AS account_no, + '' AS bank_name, + ap.settle_amount_cent AS amount_cent, + 0 AS fee_cent, + ap.settle_amount_cent AS actual_amount_cent, + CASE WHEN ap.offline_settlement_status = 'settled' THEN 'paid' ELSE 'payment_pending' END AS status, + ap.offline_settlement_status AS raw_status, + ap.completed_at AS business_created_at, + ap.offline_settled_at AS paid_at, + ap.offline_settled_by AS operator_id, + COALESCE(NULLIF(operator.nickname, ''), operator.username, '') AS operator_name, + ap.offline_settlement_remark AS remark, + '' AS category, + '' AS custom_category_name, + '' AS voucher_url, + NULL AS voided_at, + NULL AS voided_by, + '' AS voided_by_name, + '' AS void_reason, + ap.account_snapshot AS source_payload`). + Joins("LEFT JOIN admin_users AS operator ON operator.id = ap.offline_settled_by"). + Where("ap.settlement_mode = ?", "platform_managed"). + Where("ap.offline_settlement_status IN ?", []string{"pending", "settled"}). + Where("ap.settle_amount_cent > 0") + + pickupAdjustments := db.Table("admin_pickup_financial_adjustments AS fa"). + Select(`'pickup_adjustment' AS source_type, + fa.id AS source_id, + CAST(fa.id AS CHAR) AS business_no, + 0 AS order_id, + 0 AS withdrawal_id, + ap.owner_id AS user_id, + '外部卖家' AS payee_name, + '' AS payee_phone, + '' AS uploader_name, + ap.source_channel, + '' AS account_type, + '' AS account_name, + '' AS account_no, + '' AS bank_name, + fa.settle_delta_cent AS amount_cent, + 0 AS fee_cent, + fa.settle_delta_cent AS actual_amount_cent, + CASE WHEN fa.status = 'settled' THEN 'paid' ELSE 'payment_pending' END AS status, + fa.status AS raw_status, + fa.created_at AS business_created_at, + fa.settled_at AS paid_at, + fa.settled_by AS operator_id, + COALESCE(NULLIF(operator.nickname, ''), operator.username, '') AS operator_name, + fa.reason AS remark, + '' AS category, + '' AS custom_category_name, + '' AS voucher_url, + NULL AS voided_at, + NULL AS voided_by, + '' AS voided_by_name, + '' AS void_reason, + ap.account_snapshot AS source_payload`). + Joins("JOIN admin_pickups AS ap ON ap.id = fa.pickup_id"). + Joins("LEFT JOIN admin_users AS operator ON operator.id = fa.settled_by"). + Where("fa.settlement_mode = ?", "platform_managed"). + Where("fa.status IN ?", []string{"pending", "settled"}). + Where("fa.settle_delta_cent > 0") + withdrawal := db.Table("withdrawal_requests AS wr"). Select(`'withdrawal' AS source_type, wr.id AS source_id, @@ -172,7 +249,7 @@ func (r *Repository) disbursementListBaseQuery(ctx context.Context) *gorm.DB { Joins("LEFT JOIN admin_users AS creator ON creator.id = md.created_by"). Joins("LEFT JOIN admin_users AS voider ON voider.id = md.voided_by") - union := db.Raw("? UNION ALL ? UNION ALL ?", platformManaged, withdrawal, manualOffline) + union := db.Raw("? UNION ALL ? UNION ALL ? UNION ALL ? UNION ALL ?", platformManaged, pickupManaged, pickupAdjustments, withdrawal, manualOffline) return db.Table("(?) AS d", union) } diff --git a/backend/internal/modules/adminfinance/disbursement_list_test.go b/backend/internal/modules/adminfinance/disbursement_list_test.go index 1b463c9..f7292dc 100644 --- a/backend/internal/modules/adminfinance/disbursement_list_test.go +++ b/backend/internal/modules/adminfinance/disbursement_list_test.go @@ -42,6 +42,17 @@ func TestDisbursementListCombinesPlatformSettlementsAndWithdrawals(t *testing.T) status TEXT, created_by INTEGER, voided_by INTEGER, voided_at DATETIME, void_reason TEXT, created_at DATETIME )`, + `CREATE TABLE admin_pickups ( + id INTEGER PRIMARY KEY, pickup_no TEXT, owner_id INTEGER, source_channel TEXT, + settlement_mode TEXT, settle_amount_cent INTEGER, offline_settlement_status TEXT, + offline_settlement_remark TEXT, offline_settled_by INTEGER, offline_settled_at DATETIME, + completed_at DATETIME, account_snapshot BLOB + )`, + `CREATE TABLE admin_pickup_financial_adjustments ( + id INTEGER PRIMARY KEY, pickup_id INTEGER, settle_delta_cent INTEGER, + settlement_mode TEXT, status TEXT, reason TEXT, settled_by INTEGER, + settled_at DATETIME, created_at DATETIME + )`, } for _, statement := range statements { if err := db.Exec(statement).Error; err != nil { @@ -75,6 +86,17 @@ func TestDisbursementListCombinesPlatformSettlementsAndWithdrawals(t *testing.T) at(2), at(3), at(4)).Error; err != nil { t.Fatal(err) } + if err := db.Exec(`INSERT INTO admin_pickups + (id, pickup_no, owner_id, source_channel, settlement_mode, settle_amount_cent, offline_settlement_status, + offline_settlement_remark, offline_settled_by, offline_settled_at, completed_at, account_snapshot) + VALUES (30, 'P202607010001', 1, '线下录入', 'platform_managed', 4000, 'settled', '提号结算', 7, ?, ?, '{}')`, at(4), at(3)).Error; err != nil { + t.Fatal(err) + } + if err := db.Exec(`INSERT INTO admin_pickup_financial_adjustments + (id, pickup_id, settle_delta_cent, settlement_mode, status, reason, settled_by, settled_at, created_at) + VALUES (31, 30, 500, 'platform_managed', 'settled', '补差价', 7, ?, ?)`, at(5), at(5)).Error; err != nil { + t.Fatal(err) + } if err := db.Exec(`INSERT INTO withdrawal_requests (id, withdraw_no, user_id, account_type, account_name, account_no, bank_name, amount_cent, fee_cent, actual_amount_cent, status, created_at, paid_at, @@ -108,11 +130,11 @@ func TestDisbursementListCombinesPlatformSettlementsAndWithdrawals(t *testing.T) if err != nil { t.Fatalf("Disbursements() error = %v", err) } - if result.Total != 7 || len(result.Items) != 7 { - t.Fatalf("记录数 = %d/%d, want 7/7", result.Total, len(result.Items)) + if result.Total != 9 || len(result.Items) != 9 { + t.Fatalf("记录数 = %d/%d, want 9/9", result.Total, len(result.Items)) } - if result.Summary.PaidAmountCent != 24400 || result.Summary.PaidCount != 3 { - t.Fatalf("已出款 = %d/%d, want 24400/3", result.Summary.PaidAmountCent, result.Summary.PaidCount) + if result.Summary.PaidAmountCent != 28900 || result.Summary.PaidCount != 5 { + t.Fatalf("已出款 = %d/%d, want 28900/5", result.Summary.PaidAmountCent, result.Summary.PaidCount) } if result.Summary.PaymentPendingAmountCent != 11000 || result.Summary.PaymentPendingCount != 2 { t.Fatalf("待打款 = %d/%d, want 11000/2", result.Summary.PaymentPendingAmountCent, result.Summary.PaymentPendingCount) @@ -134,6 +156,16 @@ func TestDisbursementListCombinesPlatformSettlementsAndWithdrawals(t *testing.T) if platformItem.PayeeName != "外部卖家" || platformItem.PayeePhone != "13800000000" || platformItem.UploaderName != "上传客服甲" { t.Fatalf("平台收款人/上传人 = %s/%s/%s", platformItem.PayeeName, platformItem.PayeePhone, platformItem.UploaderName) } + var pickupItem *DisbursementItemDTO + for index := range result.Items { + if result.Items[index].BusinessNo == "P202607010001" { + pickupItem = &result.Items[index] + break + } + } + if pickupItem == nil || pickupItem.SourceType != "pickup" || pickupItem.ActualAmountCent != 4000 { + t.Fatalf("线下提号出款信息不正确: %+v", pickupItem) + } var manualItem *DisbursementItemDTO for index := range result.Items { diff --git a/backend/internal/modules/adminfinance/disbursement_test.go b/backend/internal/modules/adminfinance/disbursement_test.go index 47e5efb..a3458cd 100644 --- a/backend/internal/modules/adminfinance/disbursement_test.go +++ b/backend/internal/modules/adminfinance/disbursement_test.go @@ -150,6 +150,9 @@ func TestDisbursementStatisticsUseActualPaymentTimeAndAmount(t *testing.T) { if summary.PendingPaymentAmountCent != 15600 || summary.PendingPaymentCount != 4 { t.Fatalf("待出款 = %d/%d, want 15600/4", summary.PendingPaymentAmountCent, summary.PendingPaymentCount) } + if summary.OfflineSettlementPendingAmountCent != 10800 || summary.OfflineSettlementPendingCount != 3 { + t.Fatalf("线下结算待出款 = %d/%d, want 10800/3", summary.OfflineSettlementPendingAmountCent, summary.OfflineSettlementPendingCount) + } if summary.WithdrawalReviewAmountCent != 2000 || summary.WithdrawalReviewCount != 1 { t.Fatalf("待审核提现 = %d/%d, want 2000/1", summary.WithdrawalReviewAmountCent, summary.WithdrawalReviewCount) } diff --git a/backend/internal/modules/adminfinance/dto.go b/backend/internal/modules/adminfinance/dto.go index c6e005e..d5440c1 100644 --- a/backend/internal/modules/adminfinance/dto.go +++ b/backend/internal/modules/adminfinance/dto.go @@ -61,6 +61,8 @@ type PickupSummaryDTO struct { ProfitAmountCent int64 `json:"profit_amount_cent"` // 区间内已完成提号的线下利润合计 CompletedCount int64 `json:"completed_count"` // 区间内已完成提号笔数 InProgressCount int64 `json:"in_progress_count"` // 当前提号中笔数(不按时间) + PendingAmountCent int64 `json:"pending_amount_cent"` // 当前平台代管提号待结算金额(含正向调整) + PendingCount int64 `json:"pending_count"` // 当前平台代管提号待结算笔数(含正向调整) } // MohongSummaryDTO 撞车商城的交易流水,单独展示,不计入平台利润。 @@ -239,11 +241,11 @@ type FinanceSummaryDTO struct { ChannelNetAmountCent int64 `json:"channel_net_amount_cent"` // PlatformIncomeAmountCent 为正常订单最终结账利润与已完成线下提号利润之和。 // 保留原 JSON 字段名,避免影响已接入的管理端客户端。 - PlatformIncomeAmountCent int64 `json:"platform_income_amount_cent"` - NormalOrderProfitAmountCent int64 `json:"normal_order_profit_amount_cent"` - PickupProfitAmountCent int64 `json:"pickup_profit_amount_cent"` + PlatformIncomeAmountCent int64 `json:"platform_income_amount_cent"` + NormalOrderProfitAmountCent int64 `json:"normal_order_profit_amount_cent"` + PickupProfitAmountCent int64 `json:"pickup_profit_amount_cent"` // PickupCompletedCount 线下提号利润对应的已完成提号笔数。 - PickupCompletedCount int64 `json:"pickup_completed_count"` + PickupCompletedCount int64 `json:"pickup_completed_count"` OwnerShouldIncomeAmountCent int64 `json:"owner_should_income_amount_cent"` OwnerWalletIncomeAmountCent int64 `json:"owner_wallet_income_amount_cent"` OwnerEffectiveSettlementAmountCent int64 `json:"owner_effective_settlement_amount_cent"` @@ -286,20 +288,20 @@ type FinanceDailyDTO struct { // NormalFullRefundCount 正常(租赁)订单已退全款笔数(不含撞车商城退款)。 NormalFullRefundCount int64 `json:"normal_full_refund_count"` // PickupCancelledCount 线下提号取消笔数(按取消日归集)。 - PickupCancelledCount int64 `json:"pickup_cancelled_count"` - SalesCount int64 `json:"sales_count"` - PaidOrderCount int64 `json:"paid_order_count"` - OfflineSettlementPendingCount int64 `json:"offline_settlement_pending_count"` - OfflineSettlementPaidAmountCent int64 `json:"offline_settlement_paid_amount_cent"` - OfflineSettlementPaidCount int64 `json:"offline_settlement_paid_count"` - WithdrawalPaidAmountCent int64 `json:"withdrawal_paid_amount_cent"` - WithdrawalPaidCount int64 `json:"withdrawal_paid_count"` - ManualPaidAmountCent int64 `json:"manual_paid_amount_cent"` - ManualPaidCount int64 `json:"manual_paid_count"` - OperatingExpenseAmountCent int64 `json:"operating_expense_amount_cent"` - OperatingExpenseCount int64 `json:"operating_expense_count"` - DisbursementPaidAmountCent int64 `json:"disbursement_paid_amount_cent"` - DisbursementPaidCount int64 `json:"disbursement_paid_count"` + PickupCancelledCount int64 `json:"pickup_cancelled_count"` + SalesCount int64 `json:"sales_count"` + PaidOrderCount int64 `json:"paid_order_count"` + OfflineSettlementPendingCount int64 `json:"offline_settlement_pending_count"` + OfflineSettlementPaidAmountCent int64 `json:"offline_settlement_paid_amount_cent"` + OfflineSettlementPaidCount int64 `json:"offline_settlement_paid_count"` + WithdrawalPaidAmountCent int64 `json:"withdrawal_paid_amount_cent"` + WithdrawalPaidCount int64 `json:"withdrawal_paid_count"` + ManualPaidAmountCent int64 `json:"manual_paid_amount_cent"` + ManualPaidCount int64 `json:"manual_paid_count"` + OperatingExpenseAmountCent int64 `json:"operating_expense_amount_cent"` + OperatingExpenseCount int64 `json:"operating_expense_count"` + DisbursementPaidAmountCent int64 `json:"disbursement_paid_amount_cent"` + DisbursementPaidCount int64 `json:"disbursement_paid_count"` } type PaginatedResult struct { diff --git a/backend/internal/modules/adminfinance/handler.go b/backend/internal/modules/adminfinance/handler.go index 5e90f84..9841a21 100644 --- a/backend/internal/modules/adminfinance/handler.go +++ b/backend/internal/modules/adminfinance/handler.go @@ -225,7 +225,7 @@ func parseDisbursementQuery(c *gin.Context) (DisbursementQuery, bool) { StartDate: start, EndDate: end, } - if query.SourceType != "" && query.SourceType != "platform_managed" && query.SourceType != "withdrawal" && query.SourceType != "manual_offline" { + if query.SourceType != "" && query.SourceType != "platform_managed" && query.SourceType != "pickup" && query.SourceType != "pickup_adjustment" && query.SourceType != "withdrawal" && query.SourceType != "manual_offline" { response.BadRequest(c, "出款类型不正确") return query, false } diff --git a/backend/internal/modules/adminfinance/subquery.go b/backend/internal/modules/adminfinance/subquery.go index 435c925..18be7d9 100644 --- a/backend/internal/modules/adminfinance/subquery.go +++ b/backend/internal/modules/adminfinance/subquery.go @@ -25,7 +25,7 @@ func orderPaymentSubquery(db *gorm.DB) *gorm.DB { func ownerWalletIncomeSubquery(db *gorm.DB) *gorm.DB { return db.Table("wallet_ledger"). Select("order_id, COALESCE(SUM(amount_cent), 0) AS owner_wallet_income_amount_cent"). - Where("direction = ? AND biz_type IN ? AND order_id IS NOT NULL", "in", []string{"owner_income", "deposit_compensation"}). + Where("direction = ? AND biz_type IN ? AND order_id IS NOT NULL", "in", []string{"owner_income", "deposit_compensation", "arbitration_owner_income"}). Group("order_id") } @@ -39,7 +39,7 @@ func ownerWalletIncomeForSettledOrdersSubquery(db *gorm.DB, query DashboardQuery Joins(`LEFT JOIN wallet_ledger AS wallet ON wallet.order_id = scoped_order.id AND wallet.direction = ? - AND wallet.biz_type IN ?`, "in", []string{"owner_income", "deposit_compensation"}). + AND wallet.biz_type IN ?`, "in", []string{"owner_income", "deposit_compensation", "arbitration_owner_income"}). Where("scoped_order.settled_at >= ? AND scoped_order.settled_at <= ?", query.StartDate, query.EndDate). Group("scoped_order.id") } diff --git a/frontend/src/features/admin/api/adminFinance.ts b/frontend/src/features/admin/api/adminFinance.ts index ea00a22..74145c8 100644 --- a/frontend/src/features/admin/api/adminFinance.ts +++ b/frontend/src/features/admin/api/adminFinance.ts @@ -126,7 +126,7 @@ export interface FinanceDisbursementListSummary { } export interface FinanceDisbursementItem { - source_type: 'platform_managed' | 'withdrawal' | 'manual_offline' + source_type: 'platform_managed' | 'pickup' | 'pickup_adjustment' | 'withdrawal' | 'manual_offline' source_id: number business_no: string order_id?: number diff --git a/frontend/src/features/admin/views/AdminFinanceDisbursementsView.vue b/frontend/src/features/admin/views/AdminFinanceDisbursementsView.vue index 1c4e3d5..d5d86f0 100644 --- a/frontend/src/features/admin/views/AdminFinanceDisbursementsView.vue +++ b/frontend/src/features/admin/views/AdminFinanceDisbursementsView.vue @@ -274,6 +274,8 @@ function money(cents: number | undefined | null) { function sourceLabel(sourceType: string) { const labels: Record = { platform_managed: '平台代管', + pickup: '线下提号', + pickup_adjustment: '提号调整', withdrawal: '用户提现', manual_offline: '线下出款', } @@ -283,6 +285,8 @@ function sourceLabel(sourceType: string) { function sourceType(sourceType: string) { const types: Record = { platform_managed: 'primary', + pickup: 'success', + pickup_adjustment: 'warning', withdrawal: 'success', manual_offline: 'warning', } @@ -312,7 +316,10 @@ function itemStatusLabel(row: FinanceDisbursementItem) { } return map[row.raw_status] || row.status } - if (row.source_type === 'platform_managed') { + if (row.source_type === 'platform_managed' || row.source_type === 'pickup') { + return row.raw_status === 'settled' ? '已打款' : '待打款' + } + if (row.source_type === 'pickup_adjustment') { return row.raw_status === 'settled' ? '已打款' : '待打款' } return row.status === 'paid' ? '已记账' : '已作废' @@ -472,6 +479,8 @@ function accountTypeLabel(type: string) { class="full-control" > + +