feat(finance): 增加保险统计与订单保险明细

This commit is contained in:
yml
2026-11-14 17:04:24 +08:00
parent 50e3ce00f2
commit 6a2652f69d
11 changed files with 288 additions and 7 deletions
@@ -406,6 +406,18 @@ func (r *Repository) dailySettlements(ctx context.Context, query DashboardQuery)
err := db.Table("rental_orders AS ro USE INDEX (idx_rental_orders_dashboard_settled)"). err := db.Table("rental_orders AS ro USE INDEX (idx_rental_orders_dashboard_settled)").
Select(`DATE_FORMAT(ro.settled_at, '%Y-%m-%d') AS date, Select(`DATE_FORMAT(ro.settled_at, '%Y-%m-%d') AS date,
COALESCE(SUM(oc.platform_fee_cent), 0) AS platform_income_amount_cent, COALESCE(SUM(oc.platform_fee_cent), 0) AS platform_income_amount_cent,
COALESCE(SUM(CASE WHEN ro.compensation_plan = 'insurance' AND ro.insurance_fee_bps > 0
THEN 1 ELSE 0 END), 0) AS insurance_order_count,
COALESCE(SUM(CASE WHEN ro.compensation_plan = 'insurance' AND ro.insurance_fee_bps > 0
THEN COALESCE(oc.insurance_fee_cent, 0) ELSE 0 END), 0) AS insurance_profit_amount_cent,
COALESCE(SUM(CASE WHEN ro.compensation_plan = 'insurance' AND ro.insurance_fee_bps > 0
AND ro.is_premium = 0 THEN 1 ELSE 0 END), 0) AS normal_insurance_order_count,
COALESCE(SUM(CASE WHEN ro.compensation_plan = 'insurance' AND ro.insurance_fee_bps > 0
AND ro.is_premium = 0 THEN COALESCE(oc.insurance_fee_cent, 0) ELSE 0 END), 0) AS normal_insurance_profit_amount_cent,
COALESCE(SUM(CASE WHEN ro.compensation_plan = 'insurance' AND ro.insurance_fee_bps > 0
AND ro.is_premium = 1 THEN 1 ELSE 0 END), 0) AS premium_insurance_order_count,
COALESCE(SUM(CASE WHEN ro.compensation_plan = 'insurance' AND ro.insurance_fee_bps > 0
AND ro.is_premium = 1 THEN COALESCE(oc.insurance_fee_cent, 0) ELSE 0 END), 0) AS premium_insurance_profit_amount_cent,
COALESCE(SUM(oc.owner_income_amount_cent), 0) AS owner_should_income_amount_cent, COALESCE(SUM(oc.owner_income_amount_cent), 0) AS owner_should_income_amount_cent,
COALESCE(SUM(COALESCE(w.owner_wallet_income_amount_cent, 0)), 0) AS owner_wallet_income_amount_cent, COALESCE(SUM(COALESCE(w.owner_wallet_income_amount_cent, 0)), 0) AS owner_wallet_income_amount_cent,
COALESCE(SUM(CASE WHEN ro.settlement_mode = 'platform_managed' COALESCE(SUM(CASE WHEN ro.settlement_mode = 'platform_managed'
@@ -466,6 +478,12 @@ func summaryFromDailyRows(payments []dailyPaymentRow, settlements []dailySettlem
} }
for _, row := range settlements { for _, row := range settlements {
summary.NormalOrderProfitAmountCent += row.PlatformIncomeAmountCent summary.NormalOrderProfitAmountCent += row.PlatformIncomeAmountCent
summary.InsuranceOrderCount += row.InsuranceOrderCount
summary.InsuranceProfitAmountCent += row.InsuranceProfitAmountCent
summary.NormalInsuranceOrderCount += row.NormalInsuranceOrderCount
summary.NormalInsuranceProfitAmountCent += row.NormalInsuranceProfitAmountCent
summary.PremiumInsuranceOrderCount += row.PremiumInsuranceOrderCount
summary.PremiumInsuranceProfitAmountCent += row.PremiumInsuranceProfitAmountCent
summary.OwnerShouldIncomeAmountCent += row.OwnerShouldIncomeAmountCent summary.OwnerShouldIncomeAmountCent += row.OwnerShouldIncomeAmountCent
summary.OwnerWalletIncomeAmountCent += row.OwnerWalletIncomeAmountCent summary.OwnerWalletIncomeAmountCent += row.OwnerWalletIncomeAmountCent
summary.OwnerEffectiveSettlementAmountCent += row.OwnerEffectiveSettlementAmountCent summary.OwnerEffectiveSettlementAmountCent += row.OwnerEffectiveSettlementAmountCent
@@ -604,6 +622,12 @@ type dailyPaidOrderRow struct {
type dailySettlementRow struct { type dailySettlementRow struct {
Date string Date string
PlatformIncomeAmountCent int64 PlatformIncomeAmountCent int64
InsuranceOrderCount int64
InsuranceProfitAmountCent int64
NormalInsuranceOrderCount int64
NormalInsuranceProfitAmountCent int64
PremiumInsuranceOrderCount int64
PremiumInsuranceProfitAmountCent int64
OwnerShouldIncomeAmountCent int64 OwnerShouldIncomeAmountCent int64
OwnerWalletIncomeAmountCent int64 OwnerWalletIncomeAmountCent int64
OwnerEffectiveSettlementAmountCent int64 OwnerEffectiveSettlementAmountCent int64
@@ -10,7 +10,7 @@ import (
const dashboardCacheTTL = 5 * time.Minute const dashboardCacheTTL = 5 * time.Minute
func (r *Repository) dashboardCacheKey(query DashboardQuery) string { func (r *Repository) dashboardCacheKey(query DashboardQuery) string {
return fmt.Sprintf("admin-finance:dashboard:v1:%d:%d", query.StartDate.Unix(), query.EndDate.Unix()) return fmt.Sprintf("admin-finance:dashboard:v2:%d:%d", query.StartDate.Unix(), query.EndDate.Unix())
} }
func (r *Repository) loadDashboardCache(ctx context.Context, query DashboardQuery) *DashboardDTO { func (r *Repository) loadDashboardCache(ctx context.Context, query DashboardQuery) *DashboardDTO {
@@ -257,8 +257,8 @@ func TestSummaryFromDailyRowsPreservesDashboardTotals(t *testing.T) {
{TotalFlowAmountCent: 2500, TotalRefundAmountCent: 400, PendingRefundAmountCent: 70, SuccessfulPayCount: 5, PartialRefundCount: 2, PendingRefundCount: 1}, {TotalFlowAmountCent: 2500, TotalRefundAmountCent: 400, PendingRefundAmountCent: 70, SuccessfulPayCount: 5, PartialRefundCount: 2, PendingRefundCount: 1},
} }
settlementRows := []dailySettlementRow{ settlementRows := []dailySettlementRow{
{PlatformIncomeAmountCent: 300, OwnerShouldIncomeAmountCent: 900, OwnerWalletIncomeAmountCent: 850, OwnerEffectiveSettlementAmountCent: 850, OfflineSettlementAmountCent: 100, OfflineSettlementPendingAmountCent: 20, OfflineSettlementPendingCount: 1, FinancialExceptionCount: 2, SettledOrderCount: 3}, {PlatformIncomeAmountCent: 300, InsuranceOrderCount: 2, InsuranceProfitAmountCent: 80, NormalInsuranceOrderCount: 1, NormalInsuranceProfitAmountCent: 50, PremiumInsuranceOrderCount: 1, PremiumInsuranceProfitAmountCent: 30, OwnerShouldIncomeAmountCent: 900, OwnerWalletIncomeAmountCent: 850, OwnerEffectiveSettlementAmountCent: 850, OfflineSettlementAmountCent: 100, OfflineSettlementPendingAmountCent: 20, OfflineSettlementPendingCount: 1, FinancialExceptionCount: 2, SettledOrderCount: 3},
{PlatformIncomeAmountCent: 200, OwnerShouldIncomeAmountCent: 600, OwnerWalletIncomeAmountCent: 500, OwnerEffectiveSettlementAmountCent: 550, OfflineSettlementAmountCent: 200, OfflineSettlementPendingAmountCent: 40, OfflineSettlementPendingCount: 2, FinancialExceptionCount: 1, SettledOrderCount: 2}, {PlatformIncomeAmountCent: 200, InsuranceOrderCount: 1, InsuranceProfitAmountCent: 20, NormalInsuranceOrderCount: 1, NormalInsuranceProfitAmountCent: 20, OwnerShouldIncomeAmountCent: 600, OwnerWalletIncomeAmountCent: 500, OwnerEffectiveSettlementAmountCent: 550, OfflineSettlementAmountCent: 200, OfflineSettlementPendingAmountCent: 40, OfflineSettlementPendingCount: 2, FinancialExceptionCount: 1, SettledOrderCount: 2},
} }
estimateRows := []dailyEstimatedRow{ estimateRows := []dailyEstimatedRow{
{EstimatedIncomeAmountCent: 120, PendingSettleOrderCount: 4}, {EstimatedIncomeAmountCent: 120, PendingSettleOrderCount: 4},
@@ -276,6 +276,11 @@ func TestSummaryFromDailyRowsPreservesDashboardTotals(t *testing.T) {
if summary.NormalOrderProfitAmountCent != 500 || summary.PickupProfitAmountCent != 90 || summary.PlatformIncomeAmountCent != 590 || summary.PickupCompletedCount != 2 { if summary.NormalOrderProfitAmountCent != 500 || summary.PickupProfitAmountCent != 90 || summary.PlatformIncomeAmountCent != 590 || summary.PickupCompletedCount != 2 {
t.Fatalf("平台利润汇总错误: %+v", summary) t.Fatalf("平台利润汇总错误: %+v", summary)
} }
if summary.InsuranceOrderCount != 3 || summary.InsuranceProfitAmountCent != 100 ||
summary.NormalInsuranceOrderCount != 2 || summary.NormalInsuranceProfitAmountCent != 70 ||
summary.PremiumInsuranceOrderCount != 1 || summary.PremiumInsuranceProfitAmountCent != 30 {
t.Fatalf("保险汇总错误: %+v", summary)
}
if summary.OwnerShouldIncomeAmountCent != 1500 || summary.OwnerWalletIncomeAmountCent != 1350 || summary.OwnerEffectiveSettlementAmountCent != 1400 || summary.SettlementDiffAmountCent != 100 { if summary.OwnerShouldIncomeAmountCent != 1500 || summary.OwnerWalletIncomeAmountCent != 1350 || summary.OwnerEffectiveSettlementAmountCent != 1400 || summary.SettlementDiffAmountCent != 100 {
t.Fatalf("货主结算汇总错误: %+v", summary) t.Fatalf("货主结算汇总错误: %+v", summary)
} }
@@ -287,6 +292,54 @@ func TestSummaryFromDailyRowsPreservesDashboardTotals(t *testing.T) {
} }
} }
func TestInsuranceSummaryUsesSettlementDateAndPremiumSnapshot(t *testing.T) {
db := database.NewTestDB()
loc := timeutil.ShanghaiLocation()
settledAt := time.Date(2026, 8, 2, 10, 0, 0, 0, loc)
for _, row := range [][]any{
{1, "INS-NORMAL", 1, 1, 1, 1, settledAt, false, "insurance", 800},
{2, "INS-PREMIUM", 2, 2, 2, 2, settledAt, true, "insurance", 400},
{3, "NO-INSURANCE", 3, 3, 3, 3, settledAt, false, "normal", 0},
{4, "OUTSIDE-RANGE", 4, 4, 4, 4, settledAt.AddDate(0, 0, 1), true, "insurance", 400},
} {
if err := db.Exec(`INSERT INTO rental_orders
(id, order_no, listing_id, account_id, owner_id, renter_id, settled_at,
is_premium, compensation_plan, insurance_fee_bps)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, row...).Error; err != nil {
t.Fatalf("写入订单失败: %v", err)
}
}
for _, row := range [][]any{
{1, 1, 1, "accepted", 800},
{2, 2, 2, "accepted", 400},
{3, 3, 3, "accepted", 0},
{4, 4, 4, "accepted", 400},
} {
if err := db.Exec(`INSERT INTO order_checkouts
(id, order_id, initiated_by, status, insurance_fee_cent)
VALUES (?, ?, ?, ?, ?)`, row...).Error; err != nil {
t.Fatalf("写入结账单失败: %v", err)
}
}
repo := NewRepository(db)
query := DashboardQuery{
StartDate: time.Date(2026, 8, 2, 0, 0, 0, 0, loc),
EndDate: time.Date(2026, 8, 2, 23, 59, 59, 0, loc),
}
settlements, err := repo.dailySettlements(t.Context(), query)
if err != nil {
t.Fatalf("读取每日结算汇总失败: %v", err)
}
summary := summaryFromDailyRows(nil, settlements, nil)
if summary.InsuranceOrderCount != 2 || summary.InsuranceProfitAmountCent != 1200 ||
summary.NormalInsuranceOrderCount != 1 || summary.NormalInsuranceProfitAmountCent != 800 ||
summary.PremiumInsuranceOrderCount != 1 || summary.PremiumInsuranceProfitAmountCent != 400 {
t.Fatalf("保险汇总错误: %+v", summary)
}
}
func TestDashboardCacheKeyAndSingleflight(t *testing.T) { func TestDashboardCacheKeyAndSingleflight(t *testing.T) {
repo := NewRepository(nil) repo := NewRepository(nil)
q := DashboardQuery{ q := DashboardQuery{
@@ -246,6 +246,13 @@ type FinanceSummaryDTO struct {
PlatformIncomeAmountCent int64 `json:"platform_income_amount_cent"` PlatformIncomeAmountCent int64 `json:"platform_income_amount_cent"`
NormalOrderProfitAmountCent int64 `json:"normal_order_profit_amount_cent"` NormalOrderProfitAmountCent int64 `json:"normal_order_profit_amount_cent"`
PickupProfitAmountCent int64 `json:"pickup_profit_amount_cent"` PickupProfitAmountCent int64 `json:"pickup_profit_amount_cent"`
// 无忧保险统计与平台净利润同口径,按结算日归集。
InsuranceOrderCount int64 `json:"insurance_order_count"`
InsuranceProfitAmountCent int64 `json:"insurance_profit_amount_cent"`
NormalInsuranceOrderCount int64 `json:"normal_insurance_order_count"`
NormalInsuranceProfitAmountCent int64 `json:"normal_insurance_profit_amount_cent"`
PremiumInsuranceOrderCount int64 `json:"premium_insurance_order_count"`
PremiumInsuranceProfitAmountCent int64 `json:"premium_insurance_profit_amount_cent"`
// PickupCompletedCount 线下提号利润对应的已完成提号笔数。 // PickupCompletedCount 线下提号利润对应的已完成提号笔数。
PickupCompletedCount int64 `json:"pickup_completed_count"` PickupCompletedCount int64 `json:"pickup_completed_count"`
OwnerShouldIncomeAmountCent int64 `json:"owner_should_income_amount_cent"` OwnerShouldIncomeAmountCent int64 `json:"owner_should_income_amount_cent"`
@@ -13,6 +13,12 @@ export interface FinanceSummary {
platform_income_amount_cent: number platform_income_amount_cent: number
normal_order_profit_amount_cent: number normal_order_profit_amount_cent: number
pickup_profit_amount_cent: number pickup_profit_amount_cent: number
insurance_order_count: number
insurance_profit_amount_cent: number
normal_insurance_order_count: number
normal_insurance_profit_amount_cent: number
premium_insurance_order_count: number
premium_insurance_profit_amount_cent: number
/** 普通(租赁)订单利润对应的已结算订单数量 */ /** 普通(租赁)订单利润对应的已结算订单数量 */
settled_order_count: number settled_order_count: number
/** 线下提号利润对应的已完成提号笔数 */ /** 线下提号利润对应的已完成提号笔数 */
@@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest'
import { import {
estimateOrderEndAt, estimateOrderEndAt,
orderPlatformFeeBreakdown,
paymentBizTypeLabel, paymentBizTypeLabel,
paymentPaidAt, paymentPaidAt,
paymentStatusLabel, paymentStatusLabel,
@@ -39,4 +40,45 @@ describe('admin order presentation', () => {
).toBe('paid') ).toBe('paid')
expect(paymentPaidAt({ updated_at: 'updated', created_at: 'created' })).toBe('updated') expect(paymentPaidAt({ updated_at: 'updated', created_at: 'created' })).toBe('updated')
}) })
it('splits insurance from the final checkout platform income', () => {
expect(
orderPlatformFeeBreakdown({
status: 'completed',
platform_fee_cent: 3260,
insurance_fee_cent: 1739,
insurance_fee_bps: 800,
checkout: {
status: 'accepted',
platform_fee_cent: 4999,
insurance_fee_cent: 1739,
insurance_fee_bps: 800,
},
})
).toEqual({
baseFeeCent: 3260,
insuranceFeeCent: 1739,
insuranceFeeBps: 800,
totalFeeCent: 4999,
})
})
it('keeps the order snapshot before settlement and when there is no insurance', () => {
expect(
orderPlatformFeeBreakdown({
status: 'renting',
platform_fee_cent: 4000,
insurance_fee_cent: 0,
checkout: {
status: 'submitted',
platform_fee_cent: 5000,
},
})
).toEqual({
baseFeeCent: 4000,
insuranceFeeCent: 0,
insuranceFeeBps: 0,
totalFeeCent: 4000,
})
})
}) })
@@ -1,5 +1,62 @@
import type { AdminPayment } from '@/features/admin/api/adminPayments' import type { AdminPayment } from '@/features/admin/api/adminPayments'
type OrderPlatformFeeSource = {
status?: string
platform_fee_cent?: number
insurance_fee_cent?: number
insurance_fee_bps?: number
checkout?: {
status?: string
platform_fee_cent?: number
insurance_fee_cent?: number
insurance_fee_bps?: number
} | null
}
export type OrderPlatformFeeBreakdown = {
baseFeeCent: number
insuranceFeeCent: number
insuranceFeeBps: number
totalFeeCent: number
}
function nonNegativeCent(value: number | undefined) {
const amount = Number(value || 0)
return Number.isFinite(amount) ? Math.max(amount, 0) : 0
}
/**
* 订单主表保存下单时的平台基础费用,结账单保存结算后的平台收入合计。
* 已完成订单优先采用结账单,并从合计中拆出无忧保险,避免客服看到旧快照。
*/
export function orderPlatformFeeBreakdown(
order: OrderPlatformFeeSource | null | undefined
): OrderPlatformFeeBreakdown {
if (!order) {
return { baseFeeCent: 0, insuranceFeeCent: 0, insuranceFeeBps: 0, totalFeeCent: 0 }
}
const orderFeeCent = nonNegativeCent(order.platform_fee_cent)
const checkoutFeeCent = nonNegativeCent(order.checkout?.platform_fee_cent)
const totalFeeCent =
order.status === 'completed' ? Math.max(orderFeeCent, checkoutFeeCent) : orderFeeCent
const orderInsuranceFeeCent = nonNegativeCent(order.insurance_fee_cent)
const checkoutInsuranceFeeCent = nonNegativeCent(order.checkout?.insurance_fee_cent)
const insuranceFeeCent =
orderInsuranceFeeCent > 0 ? orderInsuranceFeeCent : checkoutInsuranceFeeCent
const insuranceFeeBps = Math.max(
Number(order.insurance_fee_bps || 0),
insuranceFeeCent > 0 ? Number(order.checkout?.insurance_fee_bps || 0) : 0
)
return {
baseFeeCent: Math.max(totalFeeCent - insuranceFeeCent, 0),
insuranceFeeCent: Math.min(insuranceFeeCent, totalFeeCent),
insuranceFeeBps,
totalFeeCent,
}
}
export function estimateOrderEndAt( export function estimateOrderEndAt(
estimatedEndAt?: string, estimatedEndAt?: string,
rentedAt?: string, rentedAt?: string,
@@ -386,6 +386,11 @@
color: #4f46e5; color: #4f46e5;
} }
.section-badge.amber {
background: #fffbeb;
color: #d97706;
}
.section-title { .section-title {
margin: 0; margin: 0;
font-size: 17px; font-size: 17px;
@@ -457,6 +462,20 @@
flex-wrap: wrap; flex-wrap: wrap;
} }
.insurance-type-card .sub-card-label {
display: flex;
align-items: center;
gap: 6px;
}
.insurance-type-card--normal {
border-left: 3px solid #f59e0b;
}
.insurance-type-card--premium {
border-left: 3px solid #8b5cf6;
}
.dot-sep { .dot-sep {
color: #cbd5e1; color: #cbd5e1;
} }
@@ -4,12 +4,14 @@ import {
Coin, Coin,
CreditCard, CreditCard,
DocumentChecked, DocumentChecked,
Medal,
Money, Money,
Refresh, Refresh,
Search, Search,
ShoppingCart, ShoppingCart,
Timer, Timer,
TrendCharts, TrendCharts,
Umbrella,
Van, Van,
Wallet, Wallet,
WarningFilled, WarningFilled,
@@ -370,6 +372,60 @@ function diffType(value: number | undefined | null) {
</div> </div>
</div> </div>
<!-- 无忧保险 -->
<div class="section-container">
<div class="section-header">
<div class="section-title-wrap">
<div class="section-badge amber">
<el-icon><Umbrella /></el-icon>
</div>
<div>
<h2 class="section-title">无忧保险</h2>
<p class="section-desc">按结算日统计投保订单数量与保险服务费利润</p>
</div>
</div>
</div>
<div class="sub-metric-grid cols-3">
<div class="sub-card">
<div class="sub-card-label">保险利润合计</div>
<div class="sub-card-value emerald-text">
{{ moneyCent(dashboard.summary.insurance_profit_amount_cent) }}
</div>
<div class="sub-card-sub">
<span>{{ dashboard.summary.insurance_order_count }} 笔已结算投保订单</span>
</div>
</div>
<div class="sub-card insurance-type-card insurance-type-card--normal">
<div class="sub-card-label">普通无忧保险</div>
<div class="sub-card-value">
{{ moneyCent(dashboard.summary.normal_insurance_profit_amount_cent) }}
</div>
<div class="sub-card-sub">
<span>{{ dashboard.summary.normal_insurance_order_count }} 笔投保订单</span>
<span class="dot-sep">·</span>
<span>保险利润</span>
</div>
</div>
<div class="sub-card insurance-type-card insurance-type-card--premium">
<div class="sub-card-label">
<el-icon><Medal /></el-icon>
极品专区保险
</div>
<div class="sub-card-value">
{{ moneyCent(dashboard.summary.premium_insurance_profit_amount_cent) }}
</div>
<div class="sub-card-sub">
<span>{{ dashboard.summary.premium_insurance_order_count }} 笔投保订单</span>
<span class="dot-sep">·</span>
<span>保险利润</span>
</div>
</div>
</div>
</div>
<!-- 资金出款与待办审批 --> <!-- 资金出款与待办审批 -->
<div v-if="dashboard.disbursement_summary" class="section-container"> <div v-if="dashboard.disbursement_summary" class="section-container">
<div class="section-header"> <div class="section-header">
@@ -51,7 +51,11 @@ import { formatGameName, formatListingNo } from '@/shared/utils/listingDisplay'
import AdminOrderMetrics, { type AdminOrderMetric } from '../components/AdminOrderMetrics.vue' import AdminOrderMetrics, { type AdminOrderMetric } from '../components/AdminOrderMetrics.vue'
import AdminOrderPaymentRecords from '../components/AdminOrderPaymentRecords.vue' import AdminOrderPaymentRecords from '../components/AdminOrderPaymentRecords.vue'
import ProcessTimeline from '../components/ProcessTimeline.vue' import ProcessTimeline from '../components/ProcessTimeline.vue'
import { estimateOrderEndAt, paymentStatusLabel } from '../utils/adminOrderPresentation' import {
estimateOrderEndAt,
orderPlatformFeeBreakdown,
paymentStatusLabel,
} from '../utils/adminOrderPresentation'
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
@@ -362,6 +366,7 @@ const ownerLossPriceCent = computed(() => {
return Math.max(Number(order.value.owner_rent_amount_cent) - ownerCoinBasePriceCent.value, 0) return Math.max(Number(order.value.owner_rent_amount_cent) - ownerCoinBasePriceCent.value, 0)
}) })
const hasOwnerRentBreakdown = computed(() => ownerCoinBasePriceCent.value !== null) const hasOwnerRentBreakdown = computed(() => ownerCoinBasePriceCent.value !== null)
const platformFeeBreakdown = computed(() => orderPlatformFeeBreakdown(order.value))
const fundSplitRows = computed(() => { const fundSplitRows = computed(() => {
if (!order.value) return [] if (!order.value) return []
const rows: FundSplitRow[] = [] const rows: FundSplitRow[] = []
@@ -398,8 +403,20 @@ const fundSplitRows = computed(() => {
} else { } else {
rows.push({ label: '号主租金', amountCent: order.value.owner_rent_amount_cent }) rows.push({ label: '号主租金', amountCent: order.value.owner_rent_amount_cent })
} }
const platformFee = platformFeeBreakdown.value
if (platformFee.insuranceFeeCent > 0) {
rows.push(
{ label: '平台基础费用', amountCent: platformFee.baseFeeCent },
{
label: `无忧租号保险服务费(${(platformFee.insuranceFeeBps / 100).toFixed(1)}%)`,
amountCent: platformFee.insuranceFeeCent,
},
{ label: '平台收入合计', amountCent: platformFee.totalFeeCent }
)
} else {
rows.push({ label: '平台费用', amountCent: platformFee.totalFeeCent })
}
rows.push( rows.push(
{ label: '平台费用', amountCent: order.value.platform_fee_cent },
{ label: '实付押金', amountCent: order.value.deposit_amount_cent }, { label: '实付押金', amountCent: order.value.deposit_amount_cent },
{ label: '原始押金', amountCent: order.value.deposit_original_amount_cent }, { label: '原始押金', amountCent: order.value.deposit_original_amount_cent },
{ label: '免押额度', amountCent: order.value.deposit_waived_amount_cent } { label: '免押额度', amountCent: order.value.deposit_waived_amount_cent }
+2 -2
View File
@@ -24,8 +24,8 @@ container="${TEST_MYSQL_CONTAINER:-hfb-mysql}"
[[ "${base_database}" =~ ^[A-Za-z0-9_]+$ ]] || { printf '测试库基础名不合法: %s\n' "${base_database}" >&2; exit 2; } [[ "${base_database}" =~ ^[A-Za-z0-9_]+$ ]] || { printf '测试库基础名不合法: %s\n' "${base_database}" >&2; exit 2; }
# MySQL 模式串里下划线是通配符,需要转义成字面量。 # MySQL 模式串里下划线是通配符,需要转义成字面量;只保留一层反斜杠。
pattern="$(printf '%s' "${base_database}" | sed 's/_/\\\\_/g')" pattern="$(printf '%s' "${base_database}" | sed 's/_/\\_/g')"
run_admin_sql() { run_admin_sql() {
if command -v docker >/dev/null 2>&1 && docker ps --format '{{.Names}}' 2>/dev/null | grep -qx "${container}"; then if command -v docker >/dev/null 2>&1 && docker ps --format '{{.Names}}' 2>/dev/null | grep -qx "${container}"; then