统一财务统计与线下提号出款口径
This commit is contained in:
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
// AdminPickup 管理员线下提号记录。
|
||||
// 独立于 rental_orders,不进入正常订单状态机与财务统计口径。
|
||||
// 独立于 rental_orders 和正常订单状态机;完成后进入线下提号财务统计。
|
||||
// 站内上传完成时入卖家钱包;平台代管完成后进入线下结算。
|
||||
type AdminPickup struct {
|
||||
ID uint64 `gorm:"primaryKey" json:"id"`
|
||||
|
||||
@@ -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").
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -274,6 +274,8 @@ function money(cents: number | undefined | null) {
|
||||
function sourceLabel(sourceType: string) {
|
||||
const labels: Record<string, string> = {
|
||||
platform_managed: '平台代管',
|
||||
pickup: '线下提号',
|
||||
pickup_adjustment: '提号调整',
|
||||
withdrawal: '用户提现',
|
||||
manual_offline: '线下出款',
|
||||
}
|
||||
@@ -283,6 +285,8 @@ function sourceLabel(sourceType: string) {
|
||||
function sourceType(sourceType: string) {
|
||||
const types: Record<string, 'primary' | 'success' | 'warning' | 'info' | 'danger'> = {
|
||||
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"
|
||||
>
|
||||
<el-option label="平台代管" value="platform_managed" />
|
||||
<el-option label="线下提号" value="pickup" />
|
||||
<el-option label="提号调整" value="pickup_adjustment" />
|
||||
<el-option label="用户提现" value="withdrawal" />
|
||||
<el-option label="其他线下出款" value="manual_offline" />
|
||||
</el-select>
|
||||
|
||||
Reference in New Issue
Block a user