实现外部商品调价
This commit is contained in:
@@ -0,0 +1,275 @@
|
||||
package listing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"hfb_sys/backend/internal/model"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const maxExternalSaleRatioIncrement = 10.0
|
||||
|
||||
type externalAdjustmentResult struct {
|
||||
Resources []any
|
||||
ReferenceRatio float64
|
||||
ReferenceCoinPrice float64
|
||||
SaleRatio float64
|
||||
SellerCoinPrice float64
|
||||
SellerTotalPrice float64
|
||||
ConsumablePrice float64
|
||||
ActualDiscountAmount float64
|
||||
PlatformPricing externalPlatformPricing
|
||||
}
|
||||
|
||||
func (s *Service) AdjustExternalListing(
|
||||
ctx context.Context,
|
||||
adminID uint64,
|
||||
listingID uint64,
|
||||
req AdminExternalListingAdjustRequest,
|
||||
meta AuditMeta,
|
||||
) (*ListingDTO, error) {
|
||||
if s.repo == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
}
|
||||
if listingID == 0 || req.HafCoinAmount <= 0 || req.DepositAmountCent < 0 ||
|
||||
req.AWMBullets < 0 || req.Level6Helmets < 0 || req.Level6Armor < 0 ||
|
||||
strings.TrimSpace(req.Reason) == "" {
|
||||
return nil, ErrExternalAdjustment
|
||||
}
|
||||
switch req.AdjustMode {
|
||||
case ExternalAdjustModeDiscountAmount:
|
||||
if req.DiscountAmountCent < 0 {
|
||||
return nil, ErrExternalAdjustment
|
||||
}
|
||||
case ExternalAdjustModeSaleRatio:
|
||||
if req.SaleRatio <= 0 || math.IsNaN(req.SaleRatio) || math.IsInf(req.SaleRatio, 0) {
|
||||
return nil, ErrExternalAdjustment
|
||||
}
|
||||
default:
|
||||
return nil, ErrExternalAdjustment
|
||||
}
|
||||
config, err := s.externalSalePriceConfig(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.repo.AdjustExternalListing(ctx, adminID, listingID, req, config, meta)
|
||||
}
|
||||
|
||||
func (r *Repository) AdjustExternalListing(
|
||||
ctx context.Context,
|
||||
adminID uint64,
|
||||
listingID uint64,
|
||||
req AdminExternalListingAdjustRequest,
|
||||
config salePriceConfig,
|
||||
meta AuditMeta,
|
||||
) (*ListingDTO, error) {
|
||||
if r == nil || r.db == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
}
|
||||
err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
listing, account, err := r.findForReviewUpdate(tx, listingID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if listing.InTransaction || listing.Status == "rented" || listing.Status == "completed" {
|
||||
return ErrListingLocked
|
||||
}
|
||||
switch listing.Status {
|
||||
case "draft", "published", "offline", "abnormal":
|
||||
default:
|
||||
return ErrListingLocked
|
||||
}
|
||||
var uploadCount int64
|
||||
if err := tx.Model(&model.ListingUpload{}).Where("listing_id = ?", listing.ID).Count(&uploadCount).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if uploadCount == 0 {
|
||||
return ErrExternalListingRequired
|
||||
}
|
||||
|
||||
summary := decodeAssetSummary(account.AssetSummary)
|
||||
if summary == nil {
|
||||
summary = map[string]any{}
|
||||
}
|
||||
breakdown := ensurePriceBreakdown(summary)
|
||||
referenceRatio := readSummaryNumber(breakdown["seller_reference_ratio"])
|
||||
if referenceRatio <= 0 {
|
||||
referenceRatio = readSummaryNumber(breakdown["seller_ratio"])
|
||||
}
|
||||
if referenceRatio <= 0 {
|
||||
return ErrExternalAdjustment
|
||||
}
|
||||
|
||||
before := externalAdjustmentAuditSnapshot(account, listing, summary, breakdown)
|
||||
adjustment, err := calculateExternalListingAdjustment(req, summary, referenceRatio, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if adjustment.ConsumablePrice > 0 && req.DepositAmountCent <= yuanToCent(adjustment.ConsumablePrice) {
|
||||
return ErrDepositTooLow
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
summary["resources"] = adjustment.Resources
|
||||
summary["publish_ratio"] = adjustment.PlatformPricing.BuyerRatio
|
||||
breakdown["seller_reference_ratio"] = adjustment.ReferenceRatio
|
||||
breakdown["seller_ratio"] = adjustment.SaleRatio
|
||||
breakdown["accelerated_sale_ratio"] = adjustment.SaleRatio
|
||||
breakdown["seller_coin_base_price"] = adjustment.SellerCoinPrice
|
||||
breakdown["seller_total_price"] = adjustment.SellerTotalPrice
|
||||
breakdown["consumable_price"] = adjustment.ConsumablePrice
|
||||
breakdown["buyer_coin_base_price"] = adjustment.PlatformPricing.BuyerCoinBasePrice
|
||||
breakdown["buyer_total_price"] = adjustment.PlatformPricing.BuyerTotalPrice
|
||||
breakdown["buyer_ratio"] = adjustment.PlatformPricing.BuyerRatio
|
||||
breakdown["platform_markup_amount"] = adjustment.PlatformPricing.PlatformMarkupPrice
|
||||
breakdown["platform_rule_type"] = adjustment.PlatformPricing.RuleType
|
||||
breakdown["external_admin_adjust_mode"] = req.AdjustMode
|
||||
breakdown["external_admin_discount_amount"] = adjustment.ActualDiscountAmount
|
||||
breakdown["external_admin_adjust_reason"] = strings.TrimSpace(req.Reason)
|
||||
breakdown["external_admin_adjusted_at"] = now.Format(time.RFC3339)
|
||||
breakdown["external_admin_adjusted_by"] = adminID
|
||||
summary["price_breakdown"] = breakdown
|
||||
|
||||
assetSummary, err := marshalAssetSummary(summary)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
account.HafCoinAmount = req.HafCoinAmount
|
||||
account.AssetSummary = assetSummary
|
||||
account.Title = buildExternalListingTitle(
|
||||
account.RankLevel,
|
||||
readAssetString(summary, "season_insurance"),
|
||||
float64(req.HafCoinAmount)/1000000,
|
||||
account.LoginPlatform,
|
||||
)
|
||||
listing.PriceCent = yuanToCent(adjustment.PlatformPricing.BuyerTotalPrice)
|
||||
listing.DepositAmountCent = req.DepositAmountCent
|
||||
if err := tx.Save(account).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Save(listing).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
after := externalAdjustmentAuditSnapshot(account, listing, summary, breakdown)
|
||||
return appendAuditLog(tx, adminID, "listing.adjust_external", "listing", listing.ID, meta, map[string]any{
|
||||
"listing_id": listing.ID,
|
||||
"account_id": account.ID,
|
||||
"owner_id": listing.OwnerID,
|
||||
"adjust_mode": req.AdjustMode,
|
||||
"requested_discount_amount_cent": req.DiscountAmountCent,
|
||||
"requested_sale_ratio": req.SaleRatio,
|
||||
"reason": strings.TrimSpace(req.Reason),
|
||||
"before": before,
|
||||
"after": after,
|
||||
})
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r.FindAdmin(ctx, listingID)
|
||||
}
|
||||
|
||||
func calculateExternalListingAdjustment(
|
||||
req AdminExternalListingAdjustRequest,
|
||||
summary map[string]any,
|
||||
referenceRatio float64,
|
||||
config salePriceConfig,
|
||||
) (externalAdjustmentResult, error) {
|
||||
coinM := float64(req.HafCoinAmount) / 1000000
|
||||
coinWan := float64(req.HafCoinAmount) / 10000
|
||||
referenceCoinPrice := roundMoney(coinWan / referenceRatio)
|
||||
if coinM <= 0 || coinWan <= 0 || referenceCoinPrice <= 0 {
|
||||
return externalAdjustmentResult{}, ErrExternalAdjustment
|
||||
}
|
||||
|
||||
saleRatio := roundRatio(req.SaleRatio)
|
||||
if req.AdjustMode == ExternalAdjustModeDiscountAmount {
|
||||
desiredCoinPrice := referenceCoinPrice - centToYuan(req.DiscountAmountCent)
|
||||
if desiredCoinPrice <= 0 {
|
||||
return externalAdjustmentResult{}, ErrSaleRatioOutOfRange
|
||||
}
|
||||
saleRatio = roundRatio(coinWan / desiredCoinPrice)
|
||||
}
|
||||
if saleRatio < referenceRatio || saleRatio > referenceRatio+maxExternalSaleRatioIncrement {
|
||||
return externalAdjustmentResult{}, ErrSaleRatioOutOfRange
|
||||
}
|
||||
sellerCoinPrice := roundMoney(coinWan / saleRatio)
|
||||
if sellerCoinPrice <= 0 {
|
||||
return externalAdjustmentResult{}, ErrExternalAdjustment
|
||||
}
|
||||
|
||||
coreResources := externalResources(ExternalUploadInventory{
|
||||
AWMBullets: req.AWMBullets,
|
||||
Level6Helmets: req.Level6Helmets,
|
||||
Level6Armor: req.Level6Armor,
|
||||
})
|
||||
resources := mergeExternalCoreResources(summary, coreResources)
|
||||
consumablePrice := consumableValue(map[string]any{"resources": resources})
|
||||
sellerTotalPrice := roundMoney(sellerCoinPrice + consumablePrice)
|
||||
platformPricing := calculateExternalPlatformPricing(
|
||||
coinM,
|
||||
saleRatio,
|
||||
sellerCoinPrice,
|
||||
consumablePrice,
|
||||
config,
|
||||
)
|
||||
if platformPricing.BuyerCoinBasePrice <= 0 || platformPricing.BuyerTotalPrice <= 0 || platformPricing.BuyerRatio <= 0 {
|
||||
return externalAdjustmentResult{}, ErrInvalidPrice
|
||||
}
|
||||
return externalAdjustmentResult{
|
||||
Resources: resources,
|
||||
ReferenceRatio: referenceRatio,
|
||||
ReferenceCoinPrice: referenceCoinPrice,
|
||||
SaleRatio: saleRatio,
|
||||
SellerCoinPrice: sellerCoinPrice,
|
||||
SellerTotalPrice: sellerTotalPrice,
|
||||
ConsumablePrice: consumablePrice,
|
||||
ActualDiscountAmount: roundMoney(referenceCoinPrice - sellerCoinPrice),
|
||||
PlatformPricing: platformPricing,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func mergeExternalCoreResources(summary map[string]any, core []any) []any {
|
||||
coreKeys := map[string]struct{}{"awmAmmo": {}, "helmet6": {}, "armor6": {}}
|
||||
result := append([]any{}, core...)
|
||||
rawResources, _ := summary["resources"].([]any)
|
||||
for _, raw := range rawResources {
|
||||
row, ok := raw.(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
key, _ := row["key"].(string)
|
||||
if _, isCore := coreKeys[key]; isCore {
|
||||
continue
|
||||
}
|
||||
result = append(result, row)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func externalAdjustmentAuditSnapshot(
|
||||
account *model.GameAccount,
|
||||
listing *model.RentalListing,
|
||||
summary map[string]any,
|
||||
breakdown map[string]any,
|
||||
) map[string]any {
|
||||
return map[string]any{
|
||||
"haf_coin_amount": account.HafCoinAmount,
|
||||
"awm_bullets": resourceQuantity(summary, "awmAmmo"),
|
||||
"level6_helmets": resourceQuantity(summary, "helmet6"),
|
||||
"level6_armor": resourceQuantity(summary, "armor6"),
|
||||
"deposit_amount_cent": listing.DepositAmountCent,
|
||||
"reference_ratio": readSummaryNumber(breakdown["seller_reference_ratio"]),
|
||||
"sale_ratio": readSummaryNumber(breakdown["seller_ratio"]),
|
||||
"seller_coin_base_price": readSummaryNumber(breakdown["seller_coin_base_price"]),
|
||||
"seller_total_price": readSummaryNumber(breakdown["seller_total_price"]),
|
||||
"buyer_ratio": readSummaryNumber(breakdown["buyer_ratio"]),
|
||||
"buyer_total_price": readSummaryNumber(breakdown["buyer_total_price"]),
|
||||
"listing_price_cent": listing.PriceCent,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user