统一金额分制重构
This commit is contained in:
@@ -9,45 +9,45 @@ import (
|
||||
)
|
||||
|
||||
type ListingDTO struct {
|
||||
ID uint64 `json:"id"`
|
||||
ListingNo string `json:"listing_no"`
|
||||
AccountID uint64 `json:"account_id"`
|
||||
OwnerID uint64 `json:"owner_id"`
|
||||
OwnerPhone string `json:"owner_phone,omitempty"`
|
||||
OwnerNickname string `json:"owner_nickname,omitempty"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
GameName string `json:"game_name"`
|
||||
ServerRegion string `json:"server_region"`
|
||||
LoginPlatform string `json:"login_platform"`
|
||||
RankLevel string `json:"rank_level"`
|
||||
HafCoinAmount int64 `json:"haf_coin_amount"`
|
||||
AssetSummary map[string]any `json:"asset_summary,omitempty"`
|
||||
ScreenshotURLS []string `json:"screenshot_urls"`
|
||||
CoverURL string `json:"cover_url"`
|
||||
PriceCent int64 `json:"price_cent"`
|
||||
DepositAmountCent int64 `json:"deposit_amount_cent"`
|
||||
IsAccelerated bool `json:"is_accelerated_sale"`
|
||||
InTransaction bool `json:"in_transaction"`
|
||||
Status string `json:"status"`
|
||||
ReviewStatus string `json:"review_status"`
|
||||
ReviewReason string `json:"review_reason"`
|
||||
PublishedAt *time.Time `json:"published_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
ID uint64 `json:"id"`
|
||||
ListingNo string `json:"listing_no"`
|
||||
AccountID uint64 `json:"account_id"`
|
||||
OwnerID uint64 `json:"owner_id"`
|
||||
OwnerPhone string `json:"owner_phone,omitempty"`
|
||||
OwnerNickname string `json:"owner_nickname,omitempty"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
GameName string `json:"game_name"`
|
||||
ServerRegion string `json:"server_region"`
|
||||
LoginPlatform string `json:"login_platform"`
|
||||
RankLevel string `json:"rank_level"`
|
||||
HafCoinAmount int64 `json:"haf_coin_amount"`
|
||||
AssetSummary map[string]any `json:"asset_summary,omitempty"`
|
||||
ScreenshotURLS []string `json:"screenshot_urls"`
|
||||
CoverURL string `json:"cover_url"`
|
||||
PriceCent int64 `json:"price_cent"`
|
||||
DepositAmountCent int64 `json:"deposit_amount_cent"`
|
||||
IsAccelerated bool `json:"is_accelerated_sale"`
|
||||
InTransaction bool `json:"in_transaction"`
|
||||
Status string `json:"status"`
|
||||
ReviewStatus string `json:"review_status"`
|
||||
ReviewReason string `json:"review_reason"`
|
||||
PublishedAt *time.Time `json:"published_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type CreateRequest struct {
|
||||
Title string `json:"title" binding:"required"`
|
||||
Description string `json:"description"`
|
||||
ServerRegion string `json:"server_region" binding:"required"`
|
||||
LoginPlatform string `json:"login_platform"`
|
||||
RankLevel string `json:"rank_level"`
|
||||
HafCoinAmount int64 `json:"haf_coin_amount"`
|
||||
AssetSummary map[string]any `json:"asset_summary"`
|
||||
ScreenshotURLS []string `json:"screenshot_urls"`
|
||||
Price float64 `json:"price"`
|
||||
DepositAmount float64 `json:"deposit_amount"`
|
||||
Title string `json:"title" binding:"required"`
|
||||
Description string `json:"description"`
|
||||
ServerRegion string `json:"server_region" binding:"required"`
|
||||
LoginPlatform string `json:"login_platform"`
|
||||
RankLevel string `json:"rank_level"`
|
||||
HafCoinAmount int64 `json:"haf_coin_amount"`
|
||||
AssetSummary map[string]any `json:"asset_summary"`
|
||||
ScreenshotURLS []string `json:"screenshot_urls"`
|
||||
PriceCent int64 `json:"price_cent"`
|
||||
DepositAmountCent int64 `json:"deposit_amount_cent"`
|
||||
|
||||
AgreedVirtualAssetSale bool `json:"agreed_virtual_asset_sale"`
|
||||
AgreedSellerAgreement bool `json:"agreed_seller_agreement"`
|
||||
@@ -60,9 +60,9 @@ type ReviewRequest struct {
|
||||
}
|
||||
|
||||
type AdminPriceAdjustRequest struct {
|
||||
BuyerRatio float64 `json:"buyer_ratio"`
|
||||
BuyerTotalPrice float64 `json:"buyer_total_price"`
|
||||
Reason string `json:"reason"`
|
||||
BuyerRatio float64 `json:"buyer_ratio"`
|
||||
BuyerTotalPriceCent int64 `json:"buyer_total_price_cent"`
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
|
||||
type AdminListQuery struct {
|
||||
|
||||
@@ -79,16 +79,13 @@ func (r *Repository) Create(ownerID uint64, req CreateRequest, reviewRequired bo
|
||||
if err := tx.Create(&account).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
price := normalizedListingPrice(req)
|
||||
depositAmount := roundMoney(req.DepositAmount)
|
||||
priceCent := normalizedListingPriceCent(req)
|
||||
listing := model.RentalListing{
|
||||
ListingNo: listingNo,
|
||||
AccountID: account.ID,
|
||||
OwnerID: ownerID,
|
||||
Price: price,
|
||||
PriceCent: int64(math.Round(price * 100)),
|
||||
DepositAmount: depositAmount,
|
||||
DepositAmountCent: int64(math.Round(depositAmount * 100)),
|
||||
PriceCent: priceCent,
|
||||
DepositAmountCent: req.DepositAmountCent,
|
||||
Status: listingStatus,
|
||||
ReviewStatus: reviewStatus,
|
||||
PublishedAt: publishedAt,
|
||||
@@ -149,16 +146,13 @@ func (r *Repository) CreateFromExternalUpload(upload externalUploadCreate, req C
|
||||
if err := tx.Create(&account).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
price := normalizedListingPrice(req)
|
||||
depositAmount := roundMoney(req.DepositAmount)
|
||||
priceCent := normalizedListingPriceCent(req)
|
||||
listing := model.RentalListing{
|
||||
ListingNo: listingNo,
|
||||
AccountID: account.ID,
|
||||
OwnerID: owner.ID,
|
||||
Price: price,
|
||||
PriceCent: int64(math.Round(price * 100)),
|
||||
DepositAmount: depositAmount,
|
||||
DepositAmountCent: int64(math.Round(depositAmount * 100)),
|
||||
PriceCent: priceCent,
|
||||
DepositAmountCent: req.DepositAmountCent,
|
||||
Status: "draft",
|
||||
ReviewStatus: "pending",
|
||||
}
|
||||
@@ -216,9 +210,8 @@ func (r *Repository) Update(ownerID uint64, listingID uint64, req UpdateRequest,
|
||||
}
|
||||
account.ScreenshotURLS = screenshots
|
||||
|
||||
price := normalizedListingPrice(req)
|
||||
listing.Price = price
|
||||
listing.DepositAmount = roundMoney(req.DepositAmount)
|
||||
listing.PriceCent = normalizedListingPriceCent(req)
|
||||
listing.DepositAmountCent = req.DepositAmountCent
|
||||
listingStatus, reviewStatus, publishedAt := initialPublishState(reviewRequired)
|
||||
listing.Status = listingStatus
|
||||
listing.ReviewStatus = reviewStatus
|
||||
@@ -521,7 +514,7 @@ func (r *Repository) AdjustReviewPrice(adminID uint64, listingID uint64, req Adm
|
||||
}
|
||||
sellerTotalPrice := readSummaryNumber(breakdown["seller_total_price"])
|
||||
if sellerTotalPrice <= 0 {
|
||||
sellerTotalPrice = math.Max(0, listing.Price-consumablePrice)
|
||||
sellerTotalPrice = math.Max(0, centToYuan(listing.PriceCent)-consumablePrice)
|
||||
}
|
||||
sellerCoinBasePrice := readSummaryNumber(breakdown["seller_coin_base_price"])
|
||||
if sellerCoinBasePrice <= 0 {
|
||||
@@ -536,13 +529,13 @@ func (r *Repository) AdjustReviewPrice(adminID uint64, listingID uint64, req Adm
|
||||
return ErrInvalidPrice
|
||||
}
|
||||
|
||||
beforePrice := listing.Price
|
||||
beforePriceCent := listing.PriceCent
|
||||
beforeRatio := readSummaryNumber(breakdown["buyer_ratio"])
|
||||
if beforeRatio <= 0 && listing.Price > consumablePrice {
|
||||
beforeRatio = roundRatio(coinWan / (listing.Price - consumablePrice))
|
||||
if beforeRatio <= 0 && centToYuan(listing.PriceCent) > consumablePrice {
|
||||
beforeRatio = roundRatio(coinWan / (centToYuan(listing.PriceCent) - consumablePrice))
|
||||
}
|
||||
|
||||
listing.Price = buyerTotalPrice
|
||||
listing.PriceCent = yuanToCent(buyerTotalPrice)
|
||||
summary["publish_ratio"] = buyerRatio
|
||||
breakdown["seller_coin_base_price"] = roundMoney(sellerCoinBasePrice)
|
||||
breakdown["seller_total_price"] = roundMoney(sellerTotalPrice)
|
||||
@@ -572,8 +565,8 @@ func (r *Repository) AdjustReviewPrice(adminID uint64, listingID uint64, req Adm
|
||||
"listing_id": listing.ID,
|
||||
"account_id": account.ID,
|
||||
"owner_id": listing.OwnerID,
|
||||
"before_price": beforePrice,
|
||||
"after_price": listing.Price,
|
||||
"before_price_cent": beforePriceCent,
|
||||
"after_price_cent": listing.PriceCent,
|
||||
"before_buyer_ratio": beforeRatio,
|
||||
"after_buyer_ratio": buyerRatio,
|
||||
"platform_markup": breakdown["platform_markup_amount"],
|
||||
@@ -783,9 +776,9 @@ func canListPublicWithSQL(query PublicListQuery) bool {
|
||||
func applyPublicSQLSort(db *gorm.DB, sortKey string) *gorm.DB {
|
||||
switch sortKey {
|
||||
case "priceAsc":
|
||||
return db.Order("l.price ASC, l.published_at DESC, l.id DESC")
|
||||
return db.Order("l.price_cent ASC, l.published_at DESC, l.id DESC")
|
||||
case "priceDesc":
|
||||
return db.Order("l.price DESC, l.published_at DESC, l.id DESC")
|
||||
return db.Order("l.price_cent DESC, l.published_at DESC, l.id DESC")
|
||||
case "coinDesc":
|
||||
return db.Order("a.haf_coin_amount DESC, l.published_at DESC, l.id DESC")
|
||||
default:
|
||||
@@ -1384,16 +1377,16 @@ func rowsToDTO(rows []listingRow) []ListingDTO {
|
||||
return items
|
||||
}
|
||||
|
||||
func normalizedListingPrice(req CreateRequest) float64 {
|
||||
func normalizedListingPriceCent(req CreateRequest) int64 {
|
||||
if req.AssetSummary != nil {
|
||||
if breakdown, ok := req.AssetSummary["price_breakdown"].(map[string]any); ok {
|
||||
buyerPrice := readSummaryNumber(breakdown["buyer_total_price"])
|
||||
if buyerPrice > 0 {
|
||||
return roundMoney(buyerPrice)
|
||||
return yuanToCent(buyerPrice)
|
||||
}
|
||||
}
|
||||
}
|
||||
return roundMoney(req.Price)
|
||||
return req.PriceCent
|
||||
}
|
||||
|
||||
func publicListings(items []ListingDTO) []ListingDTO {
|
||||
@@ -1620,8 +1613,8 @@ func ensurePriceBreakdown(summary map[string]any) map[string]any {
|
||||
}
|
||||
|
||||
func calculateAdminAdjustedPrice(req AdminPriceAdjustRequest, coinWan float64, consumablePrice float64) (float64, float64, float64) {
|
||||
if req.BuyerTotalPrice > 0 {
|
||||
buyerTotalPrice := roundMoney(req.BuyerTotalPrice)
|
||||
if req.BuyerTotalPriceCent > 0 {
|
||||
buyerTotalPrice := roundMoney(centToYuan(req.BuyerTotalPriceCent))
|
||||
buyerCoinBasePrice := roundMoney(buyerTotalPrice - consumablePrice)
|
||||
if buyerCoinBasePrice <= 0 || coinWan <= 0 {
|
||||
return 0, 0, 0
|
||||
@@ -1643,6 +1636,14 @@ func roundRatio(value float64) float64 {
|
||||
return math.Round(value*10) / 10
|
||||
}
|
||||
|
||||
func yuanToCent(value float64) int64 {
|
||||
return int64(math.Round(roundMoney(value) * 100))
|
||||
}
|
||||
|
||||
func centToYuan(value int64) float64 {
|
||||
return float64(value) / 100
|
||||
}
|
||||
|
||||
func cleanScreenshotURLs(urls []string) []string {
|
||||
cleaned := make([]string, 0, len(urls))
|
||||
seen := make(map[string]struct{}, len(urls))
|
||||
|
||||
@@ -252,7 +252,7 @@ func (s *Service) AdjustReviewPrice(adminID uint64, id uint64, req AdminPriceAdj
|
||||
if s.repo == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
}
|
||||
if req.BuyerRatio <= 0 && req.BuyerTotalPrice <= 0 {
|
||||
if req.BuyerRatio <= 0 && req.BuyerTotalPriceCent <= 0 {
|
||||
return nil, ErrInvalidPrice
|
||||
}
|
||||
return s.repo.AdjustReviewPrice(adminID, id, req, meta)
|
||||
@@ -328,13 +328,13 @@ func validateRequest(req CreateRequest, rules publishRules) error {
|
||||
if strings.TrimSpace(req.ServerRegion) == "" {
|
||||
return ErrMissingServerRegion
|
||||
}
|
||||
if normalizedListingPrice(req) <= 0 {
|
||||
if normalizedListingPriceCent(req) <= 0 {
|
||||
return ErrInvalidPrice
|
||||
}
|
||||
if req.DepositAmount < 0 {
|
||||
if req.DepositAmountCent < 0 {
|
||||
return ErrInvalidDeposit
|
||||
}
|
||||
if consumables := consumableValue(req.AssetSummary); consumables > 0 && req.DepositAmount <= consumables {
|
||||
if consumables := consumableValue(req.AssetSummary); consumables > 0 && req.DepositAmountCent <= yuanToCent(consumables) {
|
||||
return ErrDepositTooLow
|
||||
}
|
||||
if req.HafCoinAmount < 0 {
|
||||
@@ -525,16 +525,16 @@ func externalAccountToCreateRequest(uploaderName string, uploadTime int64, item
|
||||
},
|
||||
}
|
||||
return CreateRequest{
|
||||
Title: externalUploadTitle(item, insurance, hafCoinM),
|
||||
Description: "开放接口自动上传,等待后台审核。",
|
||||
ServerRegion: serverRegionFromLoginMethod(item.LoginMethod),
|
||||
LoginPlatform: strings.TrimSpace(item.LoginMethod),
|
||||
RankLevel: strings.TrimSpace(item.Rank),
|
||||
HafCoinAmount: int64(math.Round(hafCoinM * 1000000)),
|
||||
AssetSummary: assetSummary,
|
||||
ScreenshotURLS: []string{defaultUploadScreenshot},
|
||||
Price: price,
|
||||
DepositAmount: item.Deposit,
|
||||
Title: externalUploadTitle(item, insurance, hafCoinM),
|
||||
Description: "开放接口自动上传,等待后台审核。",
|
||||
ServerRegion: serverRegionFromLoginMethod(item.LoginMethod),
|
||||
LoginPlatform: strings.TrimSpace(item.LoginMethod),
|
||||
RankLevel: strings.TrimSpace(item.Rank),
|
||||
HafCoinAmount: int64(math.Round(hafCoinM * 1000000)),
|
||||
AssetSummary: assetSummary,
|
||||
ScreenshotURLS: []string{defaultUploadScreenshot},
|
||||
PriceCent: yuanToCent(price),
|
||||
DepositAmountCent: yuanToCent(item.Deposit),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ func TestCalculateAdminAdjustedPriceByRatio(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCalculateAdminAdjustedPriceByTotalPrice(t *testing.T) {
|
||||
base, total, ratio := calculateAdminAdjustedPrice(AdminPriceAdjustRequest{BuyerTotalPrice: 70}, 1000, 20)
|
||||
base, total, ratio := calculateAdminAdjustedPrice(AdminPriceAdjustRequest{BuyerTotalPriceCent: 7000}, 1000, 20)
|
||||
if base != 50 || total != 70 || ratio != 20 {
|
||||
t.Fatalf("unexpected adjusted price: base=%.2f total=%.2f ratio=%.2f", base, total, ratio)
|
||||
}
|
||||
@@ -35,12 +35,12 @@ func TestCalculateAdminAdjustedPriceByTotalPrice(t *testing.T) {
|
||||
|
||||
func TestValidateRequestRequiresDepositAboveConsumables(t *testing.T) {
|
||||
req := CreateRequest{
|
||||
Title: "测试账号",
|
||||
ServerRegion: "烽火地带",
|
||||
Price: 100,
|
||||
DepositAmount: 2,
|
||||
HafCoinAmount: 1000000,
|
||||
ScreenshotURLS: []string{"https://example.com/a.png"},
|
||||
Title: "测试账号",
|
||||
ServerRegion: "烽火地带",
|
||||
PriceCent: 10000,
|
||||
DepositAmountCent: 200,
|
||||
HafCoinAmount: 1000000,
|
||||
ScreenshotURLS: []string{"https://example.com/a.png"},
|
||||
AssetSummary: map[string]any{
|
||||
"resources": []any{
|
||||
map[string]any{"mode": "收费", "quantity": float64(2), "price": "1元/个"},
|
||||
@@ -52,7 +52,7 @@ func TestValidateRequestRequiresDepositAboveConsumables(t *testing.T) {
|
||||
t.Fatalf("expected ErrDepositTooLow, got %v", err)
|
||||
}
|
||||
|
||||
req.DepositAmount = 3
|
||||
req.DepositAmountCent = 300
|
||||
if err := validateRequest(req, publishRules{}); err != nil {
|
||||
t.Fatalf("expected valid request, got %v", err)
|
||||
}
|
||||
@@ -159,8 +159,8 @@ func TestExternalAccountToCreateRequestMapsUploadFields(t *testing.T) {
|
||||
if req.HafCoinAmount != 197100000 {
|
||||
t.Fatalf("expected haf coin amount 197100000, got %d", req.HafCoinAmount)
|
||||
}
|
||||
if req.Price != 458 || req.DepositAmount != 400 {
|
||||
t.Fatalf("unexpected price/deposit %.2f/%.2f", req.Price, req.DepositAmount)
|
||||
if req.PriceCent != 45800 || req.DepositAmountCent != 40000 {
|
||||
t.Fatalf("unexpected price/deposit cent %d/%d", req.PriceCent, req.DepositAmountCent)
|
||||
}
|
||||
if req.AssetSummary["season_insurance"] != "3*3" {
|
||||
t.Fatalf("expected 3*3 insurance, got %#v", req.AssetSummary["season_insurance"])
|
||||
|
||||
Reference in New Issue
Block a user