优化后台商品审核价格调整

This commit is contained in:
yml2213
2026-06-04 23:44:16 +08:00
parent c1b3dba83d
commit fbf1b37399
10 changed files with 653 additions and 33 deletions
+6
View File
@@ -55,6 +55,12 @@ type ReviewRequest struct {
Reason string `json:"reason"`
}
type AdminPriceAdjustRequest struct {
BuyerRatio float64 `json:"buyer_ratio"`
BuyerTotalPrice float64 `json:"buyer_total_price"`
Reason string `json:"reason"`
}
type AdminListQuery struct {
OwnerID uint64
Status string
@@ -203,6 +203,29 @@ func (h *Handler) Approve(c *gin.Context) {
response.OK(c, item)
}
func (h *Handler) AdjustReviewPrice(c *gin.Context) {
adminID, ok := currentAdminID(c)
if !ok {
response.Unauthorized(c, "缺少管理员上下文")
return
}
id, ok := parseID(c)
if !ok {
return
}
var req AdminPriceAdjustRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "调价参数不正确")
return
}
item, err := h.service.AdjustReviewPrice(adminID, id, req, auditMeta(c))
if err != nil {
writeListingError(c, err)
return
}
response.OK(c, item)
}
func (h *Handler) Reject(c *gin.Context) {
id, ok := parseID(c)
if !ok {
@@ -4,6 +4,7 @@ import (
"encoding/json"
"errors"
"fmt"
"math"
"net/url"
"sort"
"strconv"
@@ -472,6 +473,97 @@ func (r *Repository) Approve(listingID uint64) (*ListingDTO, error) {
return dto, err
}
func (r *Repository) AdjustReviewPrice(adminID uint64, listingID uint64, req AdminPriceAdjustRequest, meta AuditMeta) (*ListingDTO, error) {
var dto *ListingDTO
err := r.db.Transaction(func(tx *gorm.DB) error {
listing, account, err := r.findForReviewUpdate(tx, listingID)
if err != nil {
return err
}
if listing.Status == "rented" || listing.InTransaction {
return ErrListingLocked
}
summary := decodeAssetSummary(account.AssetSummary)
if summary == nil {
summary = map[string]any{}
}
breakdown := ensurePriceBreakdown(summary)
coinWan := float64(account.HafCoinAmount) / 10000
consumablePrice := readSummaryNumber(breakdown["consumable_price"])
if consumablePrice <= 0 {
consumablePrice = consumableValue(summary)
}
sellerTotalPrice := readSummaryNumber(breakdown["seller_total_price"])
if sellerTotalPrice <= 0 {
sellerTotalPrice = math.Max(0, listing.Price-consumablePrice)
}
sellerCoinBasePrice := readSummaryNumber(breakdown["seller_coin_base_price"])
if sellerCoinBasePrice <= 0 {
sellerCoinBasePrice = math.Max(0, sellerTotalPrice-consumablePrice)
}
sellerRatio := readSummaryNumber(breakdown["seller_ratio"])
if sellerRatio <= 0 && sellerCoinBasePrice > 0 {
sellerRatio = roundRatio(coinWan / sellerCoinBasePrice)
}
buyerCoinBasePrice, buyerTotalPrice, buyerRatio := calculateAdminAdjustedPrice(req, coinWan, consumablePrice)
if buyerCoinBasePrice <= 0 || buyerTotalPrice <= 0 || buyerRatio <= 0 {
return ErrInvalidPrice
}
beforePrice := listing.Price
beforeRatio := readSummaryNumber(breakdown["buyer_ratio"])
if beforeRatio <= 0 && listing.Price > consumablePrice {
beforeRatio = roundRatio(coinWan / (listing.Price - consumablePrice))
}
listing.Price = buyerTotalPrice
summary["publish_ratio"] = buyerRatio
breakdown["seller_coin_base_price"] = roundMoney(sellerCoinBasePrice)
breakdown["seller_total_price"] = roundMoney(sellerTotalPrice)
breakdown["seller_ratio"] = sellerRatio
breakdown["buyer_coin_base_price"] = buyerCoinBasePrice
breakdown["buyer_total_price"] = buyerTotalPrice
breakdown["buyer_ratio"] = buyerRatio
breakdown["platform_markup_amount"] = roundMoney(buyerTotalPrice - sellerTotalPrice)
breakdown["platform_rule_type"] = "admin_adjusted"
breakdown["admin_adjust_reason"] = strings.TrimSpace(req.Reason)
breakdown["admin_adjusted_at"] = time.Now().Format(time.RFC3339)
breakdown["admin_adjusted_by"] = adminID
summary["price_breakdown"] = breakdown
assetSummary, err := marshalAssetSummary(summary)
if err != nil {
return err
}
account.AssetSummary = assetSummary
if err := tx.Save(account).Error; err != nil {
return err
}
if err := tx.Save(listing).Error; err != nil {
return err
}
if err := appendAuditLog(tx, adminID, "listing.adjust_review_price", "listing", listing.ID, meta, map[string]any{
"listing_id": listing.ID,
"account_id": account.ID,
"owner_id": listing.OwnerID,
"before_price": beforePrice,
"after_price": listing.Price,
"before_buyer_ratio": beforeRatio,
"after_buyer_ratio": buyerRatio,
"platform_markup": breakdown["platform_markup_amount"],
"adjust_reason": req.Reason,
"buyer_coin_base": buyerCoinBasePrice,
"consumable_price": consumablePrice,
"seller_total_price": sellerTotalPrice,
}); err != nil {
return err
}
dto = toDTO(*account, *listing)
return nil
})
return dto, err
}
func (r *Repository) Reject(listingID uint64, req ReviewRequest) (*ListingDTO, error) {
var dto *ListingDTO
err := r.db.Transaction(func(tx *gorm.DB) error {
@@ -1278,6 +1370,47 @@ func readSummaryNumber(value any) float64 {
}
}
func ensurePriceBreakdown(summary map[string]any) map[string]any {
if summary == nil {
return map[string]any{}
}
breakdown, ok := summary["price_breakdown"].(map[string]any)
if ok {
return breakdown
}
breakdown = map[string]any{}
if raw, ok := summary["price_breakdown"].(map[string]interface{}); ok {
for key, value := range raw {
breakdown[key] = value
}
}
return breakdown
}
func calculateAdminAdjustedPrice(req AdminPriceAdjustRequest, coinWan float64, consumablePrice float64) (float64, float64, float64) {
if req.BuyerTotalPrice > 0 {
buyerTotalPrice := roundMoney(req.BuyerTotalPrice)
buyerCoinBasePrice := roundMoney(buyerTotalPrice - consumablePrice)
if buyerCoinBasePrice <= 0 || coinWan <= 0 {
return 0, 0, 0
}
return buyerCoinBasePrice, buyerTotalPrice, roundRatio(coinWan / buyerCoinBasePrice)
}
if req.BuyerRatio <= 0 || coinWan <= 0 {
return 0, 0, 0
}
buyerCoinBasePrice := roundMoney(coinWan / req.BuyerRatio)
buyerTotalPrice := roundMoney(buyerCoinBasePrice + consumablePrice)
return buyerCoinBasePrice, buyerTotalPrice, roundRatio(coinWan / buyerCoinBasePrice)
}
func roundRatio(value float64) float64 {
if value <= 0 || math.IsNaN(value) || math.IsInf(value, 0) {
return 0
}
return math.Round(value*10) / 10
}
func cleanScreenshotURLs(urls []string) []string {
cleaned := make([]string, 0, len(urls))
seen := make(map[string]struct{}, len(urls))
@@ -242,6 +242,16 @@ func (s *Service) Approve(id uint64) (*ListingDTO, error) {
return s.repo.Approve(id)
}
func (s *Service) AdjustReviewPrice(adminID uint64, id uint64, req AdminPriceAdjustRequest, meta AuditMeta) (*ListingDTO, error) {
if s.repo == nil {
return nil, ErrDependencyUnavailable
}
if req.BuyerRatio <= 0 && req.BuyerTotalPrice <= 0 {
return nil, ErrInvalidPrice
}
return s.repo.AdjustReviewPrice(adminID, id, req, meta)
}
func (s *Service) Reject(id uint64, req ReviewRequest) (*ListingDTO, error) {
if s.repo == nil {
return nil, ErrDependencyUnavailable
@@ -14,8 +14,22 @@ func TestConsumableValueOnlyCountsChargedResources(t *testing.T) {
},
})
if value != 2 {
t.Fatalf("expected 2, got %.2f", value)
if value != 1.5 {
t.Fatalf("expected 1.5, got %.2f", value)
}
}
func TestCalculateAdminAdjustedPriceByRatio(t *testing.T) {
base, total, ratio := calculateAdminAdjustedPrice(AdminPriceAdjustRequest{BuyerRatio: 25}, 1000, 20)
if base != 40 || total != 60 || ratio != 25 {
t.Fatalf("unexpected adjusted price: base=%.2f total=%.2f ratio=%.2f", base, total, ratio)
}
}
func TestCalculateAdminAdjustedPriceByTotalPrice(t *testing.T) {
base, total, ratio := calculateAdminAdjustedPrice(AdminPriceAdjustRequest{BuyerTotalPrice: 70}, 1000, 20)
if base != 50 || total != 70 || ratio != 20 {
t.Fatalf("unexpected adjusted price: base=%.2f total=%.2f ratio=%.2f", base, total, ratio)
}
}