242 lines
6.7 KiB
Go
242 lines
6.7 KiB
Go
package listing
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"math"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
const salePriceConfigKey = "listing.sale_price_config"
|
|
|
|
type salePriceConfig struct {
|
|
FixedMarkupRules []saleFixedMarkupRule `json:"fixed_markup_rules"`
|
|
RatioAdjustmentRules []saleRatioAdjustmentRule `json:"ratio_adjustment_rules"`
|
|
}
|
|
|
|
type saleFixedMarkupRule struct {
|
|
MinM float64 `json:"min_m"`
|
|
MaxM float64 `json:"max_m"`
|
|
MarkupAmount float64 `json:"markup_amount"`
|
|
}
|
|
|
|
type saleRatioAdjustmentRule struct {
|
|
MinM float64 `json:"min_m"`
|
|
MaxM float64 `json:"max_m"`
|
|
RatioSubtract float64 `json:"ratio_subtract"`
|
|
}
|
|
|
|
type externalPlatformPricing struct {
|
|
BuyerCoinBasePrice float64
|
|
BuyerTotalPrice float64
|
|
BuyerRatio float64
|
|
PlatformMarkupPrice float64
|
|
RuleType string
|
|
}
|
|
|
|
func (s *Service) externalSalePriceConfig(ctx context.Context) (salePriceConfig, error) {
|
|
config := defaultSalePriceConfig()
|
|
if s.config == nil {
|
|
return config, nil
|
|
}
|
|
value, err := s.config.FindValue(ctx, salePriceConfigKey)
|
|
if err != nil {
|
|
return config, err
|
|
}
|
|
if strings.TrimSpace(value) == "" {
|
|
return config, nil
|
|
}
|
|
if err := json.Unmarshal([]byte(value), &config); err != nil {
|
|
return defaultSalePriceConfig(), nil
|
|
}
|
|
return normalizeSalePriceConfig(config), nil
|
|
}
|
|
|
|
func defaultSalePriceConfig() salePriceConfig {
|
|
return salePriceConfig{
|
|
FixedMarkupRules: []saleFixedMarkupRule{
|
|
{MinM: 10, MaxM: 30, MarkupAmount: 28},
|
|
{MinM: 30, MaxM: 50, MarkupAmount: 31},
|
|
{MinM: 50, MaxM: 70, MarkupAmount: 34},
|
|
{MinM: 70, MaxM: 90, MarkupAmount: 38},
|
|
},
|
|
RatioAdjustmentRules: []saleRatioAdjustmentRule{
|
|
{MinM: 90, MaxM: 150, RatioSubtract: 5},
|
|
{MinM: 150, MaxM: 230, RatioSubtract: 4},
|
|
{MinM: 230, MaxM: 310, RatioSubtract: 3.5},
|
|
{MinM: 310, MaxM: 390, RatioSubtract: 3},
|
|
{MinM: 390, MaxM: 470, RatioSubtract: 0},
|
|
{MinM: 470, MaxM: 550, RatioSubtract: 0},
|
|
{MinM: 550, RatioSubtract: 0},
|
|
},
|
|
}
|
|
}
|
|
|
|
func normalizeSalePriceConfig(config salePriceConfig) salePriceConfig {
|
|
config.FixedMarkupRules = filterFixedMarkupRules(config.FixedMarkupRules)
|
|
config.RatioAdjustmentRules = filterRatioAdjustmentRules(config.RatioAdjustmentRules)
|
|
if len(config.FixedMarkupRules) == 0 && len(config.RatioAdjustmentRules) == 0 {
|
|
return defaultSalePriceConfig()
|
|
}
|
|
return config
|
|
}
|
|
|
|
func filterFixedMarkupRules(rules []saleFixedMarkupRule) []saleFixedMarkupRule {
|
|
result := make([]saleFixedMarkupRule, 0, len(rules))
|
|
for _, rule := range rules {
|
|
if !isFiniteNonNegative(rule.MinM) ||
|
|
!isFiniteNonNegative(rule.MaxM) ||
|
|
!isFiniteNonNegative(rule.MarkupAmount) ||
|
|
(rule.MaxM > 0 && rule.MaxM < rule.MinM) {
|
|
continue
|
|
}
|
|
result = append(result, rule)
|
|
}
|
|
return result
|
|
}
|
|
|
|
func filterRatioAdjustmentRules(rules []saleRatioAdjustmentRule) []saleRatioAdjustmentRule {
|
|
result := make([]saleRatioAdjustmentRule, 0, len(rules))
|
|
for _, rule := range rules {
|
|
if !isFiniteNonNegative(rule.MinM) ||
|
|
!isFiniteNonNegative(rule.MaxM) ||
|
|
!isFiniteNonNegative(rule.RatioSubtract) ||
|
|
(rule.MaxM > 0 && rule.MaxM < rule.MinM) {
|
|
continue
|
|
}
|
|
result = append(result, rule)
|
|
}
|
|
return result
|
|
}
|
|
|
|
func isFiniteNonNegative(value float64) bool {
|
|
return value >= 0 && !math.IsNaN(value) && !math.IsInf(value, 0)
|
|
}
|
|
|
|
// calculateExternalPlatformPricing 将外部回收租金视为纯币价格;额外物品与平台加价均单独计算。
|
|
func calculateExternalPlatformPricing(
|
|
coinM float64,
|
|
sellerRatio float64,
|
|
sellerCoinBasePrice float64,
|
|
consumablePrice float64,
|
|
config salePriceConfig,
|
|
) externalPlatformPricing {
|
|
sellerCoinBasePrice = nonNegativeMoney(sellerCoinBasePrice)
|
|
consumablePrice = nonNegativeMoney(consumablePrice)
|
|
sellerTotalPrice := roundMoney(sellerCoinBasePrice + consumablePrice)
|
|
coinWan := coinM * 100
|
|
sellerRatio = roundRatio(sellerRatio)
|
|
if sellerRatio <= 0 && coinWan > 0 && sellerCoinBasePrice > 0 {
|
|
sellerRatio = roundRatio(coinWan / sellerCoinBasePrice)
|
|
}
|
|
|
|
if rule, ok := findRatioAdjustmentRule(config.RatioAdjustmentRules, coinM); ok && sellerRatio > 0 {
|
|
buyerRatio := sellerRatio - rule.RatioSubtract
|
|
if buyerRatio > 0 && coinWan > 0 {
|
|
buyerCoinBasePrice := roundMoney(coinWan / buyerRatio)
|
|
if buyerCoinBasePrice < sellerCoinBasePrice {
|
|
buyerCoinBasePrice = sellerCoinBasePrice
|
|
}
|
|
return buildExternalPlatformPricing(
|
|
coinWan,
|
|
sellerTotalPrice,
|
|
consumablePrice,
|
|
buyerCoinBasePrice,
|
|
"ratio_subtract",
|
|
)
|
|
}
|
|
}
|
|
|
|
if rule, ok := findFixedMarkupRule(config.FixedMarkupRules, coinM); ok {
|
|
return buildExternalPlatformPricing(
|
|
coinWan,
|
|
sellerTotalPrice,
|
|
consumablePrice,
|
|
roundMoney(sellerCoinBasePrice+rule.MarkupAmount),
|
|
"fixed_markup",
|
|
)
|
|
}
|
|
|
|
return buildExternalPlatformPricing(
|
|
coinWan,
|
|
sellerTotalPrice,
|
|
consumablePrice,
|
|
sellerCoinBasePrice,
|
|
"none",
|
|
)
|
|
}
|
|
|
|
func nonNegativeMoney(value float64) float64 {
|
|
if value <= 0 || math.IsNaN(value) || math.IsInf(value, 0) {
|
|
return 0
|
|
}
|
|
return roundMoney(value)
|
|
}
|
|
|
|
func buildExternalPlatformPricing(
|
|
coinWan float64,
|
|
sellerTotalPrice float64,
|
|
consumablePrice float64,
|
|
buyerCoinBasePrice float64,
|
|
ruleType string,
|
|
) externalPlatformPricing {
|
|
buyerCoinBasePrice = nonNegativeMoney(buyerCoinBasePrice)
|
|
buyerTotalPrice := roundMoney(buyerCoinBasePrice + consumablePrice)
|
|
return externalPlatformPricing{
|
|
BuyerCoinBasePrice: buyerCoinBasePrice,
|
|
BuyerTotalPrice: buyerTotalPrice,
|
|
BuyerRatio: roundRatio(coinWan / buyerCoinBasePrice),
|
|
PlatformMarkupPrice: roundMoney(buyerTotalPrice - sellerTotalPrice),
|
|
RuleType: ruleType,
|
|
}
|
|
}
|
|
|
|
func findFixedMarkupRule(rules []saleFixedMarkupRule, coinM float64) (saleFixedMarkupRule, bool) {
|
|
sorted := append([]saleFixedMarkupRule(nil), rules...)
|
|
sort.Slice(sorted, func(i, j int) bool {
|
|
return sorted[i].MinM < sorted[j].MinM
|
|
})
|
|
for index, rule := range sorted {
|
|
if isCoinInPriceRuleRange(rule.MinM, rule.MaxM, index, len(sorted), coinM, true, false) {
|
|
return rule, true
|
|
}
|
|
}
|
|
return saleFixedMarkupRule{}, false
|
|
}
|
|
|
|
func findRatioAdjustmentRule(rules []saleRatioAdjustmentRule, coinM float64) (saleRatioAdjustmentRule, bool) {
|
|
sorted := append([]saleRatioAdjustmentRule(nil), rules...)
|
|
sort.Slice(sorted, func(i, j int) bool {
|
|
return sorted[i].MinM < sorted[j].MinM
|
|
})
|
|
for index, rule := range sorted {
|
|
if isCoinInPriceRuleRange(rule.MinM, rule.MaxM, index, len(sorted), coinM, false, true) {
|
|
return rule, true
|
|
}
|
|
}
|
|
return saleRatioAdjustmentRule{}, false
|
|
}
|
|
|
|
func isCoinInPriceRuleRange(
|
|
minM float64,
|
|
maxM float64,
|
|
index int,
|
|
total int,
|
|
coinM float64,
|
|
includeLastMax bool,
|
|
excludeFirstMin bool,
|
|
) bool {
|
|
minMatched := coinM >= minM
|
|
if excludeFirstMin && index == 0 {
|
|
minMatched = coinM > minM
|
|
}
|
|
if !minMatched {
|
|
return false
|
|
}
|
|
if maxM <= 0 || coinM < maxM {
|
|
return true
|
|
}
|
|
return includeLastMax && index == total-1 && coinM <= maxM
|
|
}
|