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, } }