线下提号 优化价格拆分
This commit is contained in:
@@ -84,19 +84,22 @@ type SellerPickupQuery struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AvailableListingDTO struct {
|
type AvailableListingDTO struct {
|
||||||
ID uint64 `json:"id"`
|
ID uint64 `json:"id"`
|
||||||
ListingNo string `json:"listing_no"`
|
ListingNo string `json:"listing_no"`
|
||||||
AccountID uint64 `json:"account_id"`
|
AccountID uint64 `json:"account_id"`
|
||||||
AccountTitle string `json:"account_title"`
|
AccountTitle string `json:"account_title"`
|
||||||
ServerRegion string `json:"server_region"`
|
ServerRegion string `json:"server_region"`
|
||||||
LoginPlatform string `json:"login_platform"`
|
LoginPlatform string `json:"login_platform"`
|
||||||
OwnerID uint64 `json:"owner_id"`
|
OwnerID uint64 `json:"owner_id"`
|
||||||
OwnerPhone string `json:"owner_phone"`
|
OwnerPhone string `json:"owner_phone"`
|
||||||
ListingPriceCent int64 `json:"listing_price_cent"`
|
ListingPriceCent int64 `json:"listing_price_cent"`
|
||||||
OwnerPriceCent int64 `json:"owner_price_cent"`
|
OwnerPriceCent int64 `json:"owner_price_cent"`
|
||||||
WebsiteProfitCent int64 `json:"website_profit_cent"`
|
OwnerPurePriceCent int64 `json:"owner_pure_price_cent"`
|
||||||
SellerRatio float64 `json:"seller_ratio"`
|
OwnerExtraItemPriceCent int64 `json:"owner_extra_item_price_cent"`
|
||||||
BuyerRatio float64 `json:"buyer_ratio"`
|
OwnerTotalPriceCent int64 `json:"owner_total_price_cent"`
|
||||||
|
WebsiteProfitCent int64 `json:"website_profit_cent"`
|
||||||
|
SellerRatio float64 `json:"seller_ratio"`
|
||||||
|
BuyerRatio float64 `json:"buyer_ratio"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PaginatedResult struct {
|
type PaginatedResult struct {
|
||||||
|
|||||||
@@ -452,19 +452,22 @@ func (row availableListingRow) toDTO() AvailableListingDTO {
|
|||||||
model.GameAccount{HafCoinAmount: row.HafCoinAmount, AssetSummary: row.AssetSummary},
|
model.GameAccount{HafCoinAmount: row.HafCoinAmount, AssetSummary: row.AssetSummary},
|
||||||
)
|
)
|
||||||
return AvailableListingDTO{
|
return AvailableListingDTO{
|
||||||
ID: row.ID,
|
ID: row.ID,
|
||||||
ListingNo: row.ListingNo,
|
ListingNo: row.ListingNo,
|
||||||
AccountID: row.AccountID,
|
AccountID: row.AccountID,
|
||||||
AccountTitle: row.AccountTitle,
|
AccountTitle: row.AccountTitle,
|
||||||
ServerRegion: row.ServerRegion,
|
ServerRegion: row.ServerRegion,
|
||||||
LoginPlatform: row.LoginPlatform,
|
LoginPlatform: row.LoginPlatform,
|
||||||
OwnerID: row.OwnerID,
|
OwnerID: row.OwnerID,
|
||||||
OwnerPhone: row.OwnerPhone,
|
OwnerPhone: row.OwnerPhone,
|
||||||
ListingPriceCent: snapshot.ListingPriceCent,
|
ListingPriceCent: snapshot.ListingPriceCent,
|
||||||
OwnerPriceCent: snapshot.OwnerPriceCent,
|
OwnerPriceCent: snapshot.OwnerPriceCent,
|
||||||
WebsiteProfitCent: snapshot.WebsiteProfitCent,
|
OwnerPurePriceCent: snapshot.OwnerPurePriceCent,
|
||||||
SellerRatio: snapshot.SellerRatio,
|
OwnerExtraItemPriceCent: snapshot.OwnerExtraItemPriceCent,
|
||||||
BuyerRatio: snapshot.BuyerRatio,
|
OwnerTotalPriceCent: snapshot.OwnerTotalPriceCent,
|
||||||
|
WebsiteProfitCent: snapshot.WebsiteProfitCent,
|
||||||
|
SellerRatio: snapshot.SellerRatio,
|
||||||
|
BuyerRatio: snapshot.BuyerRatio,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -506,24 +509,37 @@ func isEmptyPickupAccountSnapshot(raw datatypes.JSON) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type pickupPriceSnapshot struct {
|
type pickupPriceSnapshot struct {
|
||||||
ListingPriceCent int64
|
ListingPriceCent int64
|
||||||
OwnerPriceCent int64
|
OwnerPriceCent int64
|
||||||
WebsiteProfitCent int64
|
OwnerPurePriceCent int64
|
||||||
SellerRatio float64
|
OwnerExtraItemPriceCent int64
|
||||||
BuyerRatio float64
|
OwnerTotalPriceCent int64
|
||||||
|
WebsiteProfitCent int64
|
||||||
|
SellerRatio float64
|
||||||
|
BuyerRatio float64
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildPickupPriceSnapshot(listing model.RentalListing, account model.GameAccount) pickupPriceSnapshot {
|
func buildPickupPriceSnapshot(listing model.RentalListing, account model.GameAccount) pickupPriceSnapshot {
|
||||||
summary := decodePickupAssetSummary(account.AssetSummary)
|
summary := decodePickupAssetSummary(account.AssetSummary)
|
||||||
breakdown := pickupPriceBreakdown(summary)
|
breakdown := pickupPriceBreakdown(summary)
|
||||||
listingPriceCent := maxPickupCent(listing.PriceCent, 0)
|
listingPriceCent := maxPickupCent(listing.PriceCent, 0)
|
||||||
ownerPriceCent := yuanToPickupCent(readPickupNumber(breakdown["seller_total_price"]))
|
ownerPurePriceCent := yuanToPickupCent(readPickupNumber(breakdown["seller_coin_base_price"]))
|
||||||
if ownerPriceCent <= 0 || (listingPriceCent > 0 && ownerPriceCent > listingPriceCent) {
|
ownerExtraItemPriceCent := yuanToPickupCent(readPickupNumber(breakdown["consumable_price"]))
|
||||||
ownerPriceCent = listingPriceCent
|
ownerTotalPriceCent := yuanToPickupCent(readPickupNumber(breakdown["seller_total_price"]))
|
||||||
|
if ownerTotalPriceCent <= 0 && (ownerPurePriceCent > 0 || ownerExtraItemPriceCent > 0) {
|
||||||
|
ownerTotalPriceCent = ownerPurePriceCent + ownerExtraItemPriceCent
|
||||||
}
|
}
|
||||||
|
if ownerTotalPriceCent <= 0 || (listingPriceCent > 0 && ownerTotalPriceCent > listingPriceCent) {
|
||||||
|
ownerTotalPriceCent = listingPriceCent
|
||||||
|
}
|
||||||
|
ownerPurePriceCent, ownerExtraItemPriceCent = normalizePickupOwnerPriceSplit(
|
||||||
|
ownerTotalPriceCent,
|
||||||
|
ownerPurePriceCent,
|
||||||
|
ownerExtraItemPriceCent,
|
||||||
|
)
|
||||||
websiteProfitCent := yuanToPickupCent(readPickupNumber(breakdown["platform_markup_amount"]))
|
websiteProfitCent := yuanToPickupCent(readPickupNumber(breakdown["platform_markup_amount"]))
|
||||||
if websiteProfitCent <= 0 {
|
if websiteProfitCent <= 0 {
|
||||||
websiteProfitCent = listingPriceCent - ownerPriceCent
|
websiteProfitCent = listingPriceCent - ownerTotalPriceCent
|
||||||
}
|
}
|
||||||
if websiteProfitCent < 0 {
|
if websiteProfitCent < 0 {
|
||||||
websiteProfitCent = 0
|
websiteProfitCent = 0
|
||||||
@@ -540,14 +556,41 @@ func buildPickupPriceSnapshot(listing model.RentalListing, account model.GameAcc
|
|||||||
buyerRatio = ratioFromCoin(account.HafCoinAmount, readPickupNumber(breakdown["buyer_coin_base_price"]))
|
buyerRatio = ratioFromCoin(account.HafCoinAmount, readPickupNumber(breakdown["buyer_coin_base_price"]))
|
||||||
}
|
}
|
||||||
return pickupPriceSnapshot{
|
return pickupPriceSnapshot{
|
||||||
ListingPriceCent: listingPriceCent,
|
ListingPriceCent: listingPriceCent,
|
||||||
OwnerPriceCent: ownerPriceCent,
|
OwnerPriceCent: ownerTotalPriceCent,
|
||||||
WebsiteProfitCent: websiteProfitCent,
|
OwnerPurePriceCent: ownerPurePriceCent,
|
||||||
SellerRatio: roundPickupRatio(sellerRatio),
|
OwnerExtraItemPriceCent: ownerExtraItemPriceCent,
|
||||||
BuyerRatio: roundPickupRatio(buyerRatio),
|
OwnerTotalPriceCent: ownerTotalPriceCent,
|
||||||
|
WebsiteProfitCent: websiteProfitCent,
|
||||||
|
SellerRatio: roundPickupRatio(sellerRatio),
|
||||||
|
BuyerRatio: roundPickupRatio(buyerRatio),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func normalizePickupOwnerPriceSplit(totalCent, pureCent, extraCent int64) (int64, int64) {
|
||||||
|
if totalCent <= 0 {
|
||||||
|
return 0, 0
|
||||||
|
}
|
||||||
|
pureCent = maxPickupCent(pureCent, 0)
|
||||||
|
extraCent = maxPickupCent(extraCent, 0)
|
||||||
|
switch {
|
||||||
|
case pureCent == 0 && extraCent == 0:
|
||||||
|
pureCent = totalCent
|
||||||
|
case pureCent == 0:
|
||||||
|
pureCent = maxPickupCent(totalCent-extraCent, 0)
|
||||||
|
case extraCent == 0:
|
||||||
|
extraCent = maxPickupCent(totalCent-pureCent, 0)
|
||||||
|
case pureCent+extraCent != totalCent:
|
||||||
|
if pureCent > totalCent {
|
||||||
|
pureCent = totalCent
|
||||||
|
extraCent = 0
|
||||||
|
} else {
|
||||||
|
extraCent = totalCent - pureCent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pureCent, extraCent
|
||||||
|
}
|
||||||
|
|
||||||
func decodePickupAssetSummary(raw datatypes.JSON) map[string]any {
|
func decodePickupAssetSummary(raw datatypes.JSON) map[string]any {
|
||||||
if len(raw) == 0 {
|
if len(raw) == 0 {
|
||||||
return map[string]any{}
|
return map[string]any{}
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package pickup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"hfb_sys/backend/internal/model"
|
||||||
|
|
||||||
|
"gorm.io/datatypes"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBuildPickupPriceSnapshotSplitsOwnerPrice(t *testing.T) {
|
||||||
|
raw, err := json.Marshal(map[string]any{
|
||||||
|
"price_breakdown": map[string]any{
|
||||||
|
"seller_coin_base_price": 233,
|
||||||
|
"seller_total_price": 353,
|
||||||
|
"platform_markup_amount": 30,
|
||||||
|
"consumable_price": 120,
|
||||||
|
"seller_ratio": 42.9,
|
||||||
|
"buyer_ratio": 35.1,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("构造资产快照失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshot := buildPickupPriceSnapshot(
|
||||||
|
model.RentalListing{PriceCent: 38300},
|
||||||
|
model.GameAccount{HafCoinAmount: 100000000, AssetSummary: datatypes.JSON(raw)},
|
||||||
|
)
|
||||||
|
|
||||||
|
assertInt64(t, "网站售价", snapshot.ListingPriceCent, 38300)
|
||||||
|
assertInt64(t, "号主纯比价格", snapshot.OwnerPurePriceCent, 23300)
|
||||||
|
assertInt64(t, "额外物品价格", snapshot.OwnerExtraItemPriceCent, 12000)
|
||||||
|
assertInt64(t, "号主总价", snapshot.OwnerTotalPriceCent, 35300)
|
||||||
|
assertInt64(t, "兼容号主价", snapshot.OwnerPriceCent, 35300)
|
||||||
|
assertInt64(t, "网站加价", snapshot.WebsiteProfitCent, 3000)
|
||||||
|
if snapshot.SellerRatio != 42.9 || snapshot.BuyerRatio != 35.1 {
|
||||||
|
t.Fatalf("比例 = %.2f/%.2f, want 42.90/35.10", snapshot.SellerRatio, snapshot.BuyerRatio)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildPickupPriceSnapshotFallsBackToSplitSum(t *testing.T) {
|
||||||
|
raw, err := json.Marshal(map[string]any{
|
||||||
|
"price_breakdown": map[string]any{
|
||||||
|
"seller_coin_base_price": 200,
|
||||||
|
"consumable_price": 50,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("构造资产快照失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshot := buildPickupPriceSnapshot(
|
||||||
|
model.RentalListing{PriceCent: 30000},
|
||||||
|
model.GameAccount{AssetSummary: datatypes.JSON(raw)},
|
||||||
|
)
|
||||||
|
|
||||||
|
assertInt64(t, "拆分合计总价", snapshot.OwnerTotalPriceCent, 25000)
|
||||||
|
assertInt64(t, "纯比价格", snapshot.OwnerPurePriceCent, 20000)
|
||||||
|
assertInt64(t, "额外物品价格", snapshot.OwnerExtraItemPriceCent, 5000)
|
||||||
|
assertInt64(t, "网站加价兜底", snapshot.WebsiteProfitCent, 5000)
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertInt64(t *testing.T, name string, got, want int64) {
|
||||||
|
t.Helper()
|
||||||
|
if got != want {
|
||||||
|
t.Fatalf("%s = %d, want %d", name, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,6 +41,9 @@ export interface AvailableListing {
|
|||||||
owner_phone: string
|
owner_phone: string
|
||||||
listing_price_cent: number
|
listing_price_cent: number
|
||||||
owner_price_cent: number
|
owner_price_cent: number
|
||||||
|
owner_pure_price_cent: number
|
||||||
|
owner_extra_item_price_cent: number
|
||||||
|
owner_total_price_cent: number
|
||||||
website_profit_cent: number
|
website_profit_cent: number
|
||||||
seller_ratio: number
|
seller_ratio: number
|
||||||
buyer_ratio: number
|
buyer_ratio: number
|
||||||
|
|||||||
@@ -38,6 +38,21 @@ const listingLoading = ref(false)
|
|||||||
const selectedListing = computed(
|
const selectedListing = computed(
|
||||||
() => listingOptions.value.find(item => item.id === createForm.listing_id) || null
|
() => listingOptions.value.find(item => item.id === createForm.listing_id) || null
|
||||||
)
|
)
|
||||||
|
const selectedPriceSnapshot = computed(() => {
|
||||||
|
const item = selectedListing.value
|
||||||
|
if (!item) return null
|
||||||
|
const totalCent = normalizeCent(item.owner_total_price_cent || item.owner_price_cent)
|
||||||
|
let pureCent = normalizeCent(item.owner_pure_price_cent)
|
||||||
|
let extraCent = normalizeCent(item.owner_extra_item_price_cent)
|
||||||
|
if (pureCent <= 0 && extraCent <= 0) {
|
||||||
|
pureCent = totalCent
|
||||||
|
} else if (pureCent <= 0) {
|
||||||
|
pureCent = Math.max(totalCent - extraCent, 0)
|
||||||
|
} else if (extraCent <= 0) {
|
||||||
|
extraCent = Math.max(totalCent - pureCent, 0)
|
||||||
|
}
|
||||||
|
return { pureCent, extraCent, totalCent }
|
||||||
|
})
|
||||||
|
|
||||||
onMounted(loadList)
|
onMounted(loadList)
|
||||||
|
|
||||||
@@ -193,6 +208,16 @@ function ratioText(value: number) {
|
|||||||
const rounded = Math.round(ratio * 100) / 100
|
const rounded = Math.round(ratio * 100) / 100
|
||||||
return `1:${Number.isInteger(rounded) ? rounded : rounded.toFixed(2)}`
|
return `1:${Number.isInteger(rounded) ? rounded : rounded.toFixed(2)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeCent(value: number | undefined | null) {
|
||||||
|
const cent = Number(value || 0)
|
||||||
|
if (!Number.isFinite(cent)) return 0
|
||||||
|
return Math.max(Math.round(cent), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
function listingOwnerTotalCent(item: AvailableListing) {
|
||||||
|
return normalizeCent(item.owner_total_price_cent || item.owner_price_cent)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -317,15 +342,17 @@ function ratioText(value: number) {
|
|||||||
<el-option
|
<el-option
|
||||||
v-for="item in listingOptions"
|
v-for="item in listingOptions"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
:label="`${item.listing_no} · ${item.account_title} · ${formatCentWithSymbol(item.owner_price_cent)} / ${formatCentWithSymbol(item.listing_price_cent)}`"
|
:label="`${item.listing_no} · ${item.account_title} · ${formatCentWithSymbol(listingOwnerTotalCent(item))} / ${formatCentWithSymbol(item.listing_price_cent)}`"
|
||||||
:value="item.id"
|
:value="item.id"
|
||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
<div class="form-hint">仅显示已发布、已审核、未在交易中的账号</div>
|
<div class="form-hint">仅显示已发布、已审核、未在交易中的账号</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item v-if="selectedListing" label="价格快照">
|
<el-form-item v-if="selectedListing && selectedPriceSnapshot" label="价格快照">
|
||||||
<div class="price-snapshot">
|
<div class="price-snapshot">
|
||||||
<span>号主价 {{ formatCentWithSymbol(selectedListing.owner_price_cent) }}</span>
|
<span>纯比价格 {{ formatCentWithSymbol(selectedPriceSnapshot.pureCent) }}</span>
|
||||||
|
<span>额外物品价格 {{ formatCentWithSymbol(selectedPriceSnapshot.extraCent) }}</span>
|
||||||
|
<span>号主总价 {{ formatCentWithSymbol(selectedPriceSnapshot.totalCent) }}</span>
|
||||||
<span>网站售价 {{ formatCentWithSymbol(selectedListing.listing_price_cent) }}</span>
|
<span>网站售价 {{ formatCentWithSymbol(selectedListing.listing_price_cent) }}</span>
|
||||||
<span>网站加价 {{ formatCentWithSymbol(selectedListing.website_profit_cent) }}</span>
|
<span>网站加价 {{ formatCentWithSymbol(selectedListing.website_profit_cent) }}</span>
|
||||||
<span>回收 {{ ratioText(selectedListing.seller_ratio) }}</span>
|
<span>回收 {{ ratioText(selectedListing.seller_ratio) }}</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user