Files
hfb_jysh/backend/internal/modules/order/pricing.go
T

645 lines
22 KiB
Go

package order
import (
"encoding/json"
"math"
"strconv"
"time"
"hfb_sys/backend/internal/model"
"hfb_sys/backend/internal/modules/rentergrowth"
"gorm.io/datatypes"
)
type orderPricing struct {
RentAmountCent int64
OwnerRentAmountCent int64
PlatformFeeCent int64
PureCoinAmountCent int64
OwnerPureCoinAmountCent int64
ExtraItemAmountCent int64
OwnerExtraItemAmountCent int64
PureCoinPlatformFeeCent int64
}
type checkoutSettlement struct {
OwnerRentIncomeCent int64
DepositCompensationCent int64
OwnerIncomeCent int64
RentRefundCent int64
DepositRefundCent int64
RenterRefundCent int64
PlatformFeeCent int64
ActualRentAmountCent int64
PureCoinAmountCent int64
PureCoinDiscountCent int64
PureCoinPayableCent int64
ExtraItemAmountCent int64
CoinConsumedM float64
OvershootAmountCent int64
ShortfallCent int64
}
// ActualPureCoinAmounts 是跨正常结账与仲裁共用的最终纯币计价结果。
type ActualPureCoinAmounts struct {
AmountCent int64
DiscountCent int64
PayableCent int64
ConsumedM float64
}
// ActualCheckoutSettlement 是正常结账与仲裁共用的实际消耗结算结果。
type ActualCheckoutSettlement struct {
OwnerRentIncomeCent int64
DepositCompensationCent int64
OwnerIncomeCent int64
RentRefundCent int64
DepositRefundCent int64
RenterRefundCent int64
PlatformFeeCent int64
ActualRentAmountCent int64
PureCoinAmountCent int64
PureCoinDiscountCent int64
PureCoinPayableCent int64
ExtraItemAmountCent int64
CoinConsumedM float64
OvershootAmountCent int64
ShortfallCent int64
}
// CalculateActualPureCoinAmounts 按订单价格快照和实际消耗 M 计算纯币原价及等级优惠。
func CalculateActualPureCoinAmounts(order model.RentalOrder, coinConsumedM float64) ActualPureCoinAmounts {
settlement := CalculateActualCheckoutSettlement(order, 0, coinConsumedM, 0)
return ActualPureCoinAmounts{
AmountCent: settlement.PureCoinAmountCent,
DiscountCent: settlement.PureCoinDiscountCent,
PayableCent: settlement.PureCoinPayableCent,
ConsumedM: settlement.CoinConsumedM,
}
}
// CalculateActualCheckoutSettlement 按实际纯币和额外物品消耗计算完整结算。
func CalculateActualCheckoutSettlement(order model.RentalOrder, consumableAmountCent int64, coinConsumedM float64, depositDeductAmountCent int64) ActualCheckoutSettlement {
return toActualCheckoutSettlement(calculateCheckoutSettlement(order, consumableAmountCent, coinConsumedM, depositDeductAmountCent))
}
func toActualCheckoutSettlement(settlement checkoutSettlement) ActualCheckoutSettlement {
return ActualCheckoutSettlement{
OwnerRentIncomeCent: settlement.OwnerRentIncomeCent,
DepositCompensationCent: settlement.DepositCompensationCent,
OwnerIncomeCent: settlement.OwnerIncomeCent,
RentRefundCent: settlement.RentRefundCent,
DepositRefundCent: settlement.DepositRefundCent,
RenterRefundCent: settlement.RenterRefundCent,
PlatformFeeCent: settlement.PlatformFeeCent,
ActualRentAmountCent: settlement.ActualRentAmountCent,
PureCoinAmountCent: settlement.PureCoinAmountCent,
PureCoinDiscountCent: settlement.PureCoinDiscountCent,
PureCoinPayableCent: settlement.PureCoinPayableCent,
ExtraItemAmountCent: settlement.ExtraItemAmountCent,
CoinConsumedM: settlement.CoinConsumedM,
OvershootAmountCent: settlement.OvershootAmountCent,
ShortfallCent: settlement.ShortfallCent,
}
}
func orderDurationHours(order model.RentalOrder) int {
if order.EstimatedDurationHours > 0 {
return order.EstimatedDurationHours
}
return internalOrderHours
}
func orderEstimatedEndAt(order model.RentalOrder) *time.Time {
if order.RentedAt == nil {
return nil
}
durationHours := orderDurationHours(order)
if durationHours <= 0 {
return nil
}
endAt := order.RentedAt.Add(time.Duration(durationHours) * time.Hour)
return &endAt
}
// orderListingTerms 用下单快照里的哈夫币和日损耗估算租期,和首页「可租 / 消耗」同一口径。
func orderListingTerms(snapshot datatypes.JSON) (days int, dailyLossM float64) {
dailyLossM = readOrderSnapshotAssetNumber(snapshot, "daily_loss_m")
coinM := readOrderSnapshotCoinM(snapshot)
if dailyLossM <= 0 || coinM <= 0 {
return 0, 0
}
days = int(math.Ceil(coinM / dailyLossM))
if days < 1 {
days = 1
}
return days, roundQuantity(dailyLossM)
}
func estimateOrderDurationHours(snapshot datatypes.JSON) int {
hafCoinM := readOrderSnapshotCoinM(snapshot)
if hafCoinM <= 0 {
return internalOrderHours
}
dailyLossM := readOrderSnapshotAssetNumber(snapshot, "daily_loss_m")
if dailyLossM <= 0 {
return internalOrderHours
}
days := hafCoinM / dailyLossM
if days <= 0 {
return internalOrderHours
}
// 不足整天按整天向上取整,例如 1.3 / 1.7 天均按 2 天计
daysCeil := int(math.Ceil(days))
if daysCeil < 1 {
daysCeil = 1
}
hours := daysCeil * 24
return hours
}
func buildOrderPricing(listing model.RentalListing, account model.GameAccount) orderPricing {
rentAmountCent := listing.PriceCent
ownerRentAmountCent := readSnapshotCent(account.AssetSummary, "seller_total_price")
if ownerRentAmountCent <= 0 || ownerRentAmountCent > rentAmountCent {
ownerRentAmountCent = rentAmountCent
}
platformFeeCent := readSnapshotCent(account.AssetSummary, "platform_markup_amount")
if platformFeeCent <= 0 || ownerRentAmountCent+platformFeeCent != rentAmountCent {
platformFeeCent = rentAmountCent - ownerRentAmountCent
}
if platformFeeCent < 0 {
platformFeeCent = 0
}
pureCoinAmountCent := readSnapshotCent(account.AssetSummary, "buyer_coin_base_price")
extraItemSnapshotCent := readSnapshotCent(account.AssetSummary, "consumable_price")
if pureCoinAmountCent <= 0 {
if extraItemSnapshotCent > 0 && extraItemSnapshotCent <= rentAmountCent {
pureCoinAmountCent = rentAmountCent - extraItemSnapshotCent
} else {
pureCoinAmountCent = rentAmountCent
}
}
if pureCoinAmountCent < 0 || pureCoinAmountCent > rentAmountCent {
pureCoinAmountCent = rentAmountCent
}
extraItemAmountCent := maxCent(rentAmountCent-pureCoinAmountCent, 0)
ownerPureCoinAmountCent := readSnapshotCent(account.AssetSummary, "seller_coin_base_price")
if ownerPureCoinAmountCent <= 0 || ownerPureCoinAmountCent > ownerRentAmountCent {
ownerPureCoinAmountCent = maxCent(ownerRentAmountCent-minCent(extraItemAmountCent, ownerRentAmountCent), 0)
}
ownerExtraItemAmountCent := maxCent(ownerRentAmountCent-ownerPureCoinAmountCent, 0)
pureCoinPlatformFeeCent := maxCent(pureCoinAmountCent-ownerPureCoinAmountCent, 0)
return orderPricing{
RentAmountCent: rentAmountCent,
OwnerRentAmountCent: ownerRentAmountCent,
PlatformFeeCent: platformFeeCent,
PureCoinAmountCent: pureCoinAmountCent,
OwnerPureCoinAmountCent: ownerPureCoinAmountCent,
ExtraItemAmountCent: extraItemAmountCent,
OwnerExtraItemAmountCent: ownerExtraItemAmountCent,
PureCoinPlatformFeeCent: pureCoinPlatformFeeCent,
}
}
func readSnapshotPrice(raw datatypes.JSON, key string) float64 {
return readOrderSnapshotPrice(raw, key)
}
// readSnapshotCent 优先读取 price_breakdown 的整数分字段,缺失时兼容旧元字段。
func readSnapshotCent(raw datatypes.JSON, key string) int64 {
return readOrderSnapshotCent(raw, key)
}
func readOrderSnapshotCent(raw datatypes.JSON, key string) int64 {
breakdown, ok := readOrderSnapshotBreakdown(raw)
if !ok {
return 0
}
if cent := readJSONInt64(breakdown[key+"_cent"]); cent > 0 {
return cent
}
legacy := readJSONNumber(breakdown[key])
if legacy <= 0 {
return 0
}
return int64(math.Round(legacy * 100))
}
func readOrderSnapshotBreakdown(raw datatypes.JSON) (map[string]any, bool) {
if len(raw) == 0 {
return nil, false
}
var snapshot map[string]any
if err := json.Unmarshal(raw, &snapshot); err != nil {
return nil, false
}
if assetSummary, ok := snapshot["asset_summary"].(map[string]any); ok {
snapshot = assetSummary
}
breakdown, ok := snapshot["price_breakdown"].(map[string]any)
return breakdown, ok
}
func readJSONInt64(value any) int64 {
switch typed := value.(type) {
case float64:
return int64(typed)
case float32:
return int64(typed)
case int:
return int64(typed)
case int64:
return typed
case int32:
return int64(typed)
case json.Number:
number, err := typed.Int64()
if err != nil {
return 0
}
return number
case string:
number, err := strconv.ParseInt(typed, 10, 64)
if err != nil {
return 0
}
return number
default:
return 0
}
}
func readOrderSnapshotPrice(raw datatypes.JSON, key string) float64 {
breakdown, ok := readOrderSnapshotBreakdown(raw)
if !ok {
return 0
}
return readJSONNumber(breakdown[key])
}
// readOrderSnapshotBPS 优先读取整数基点字段;旧快照只有 float 比例时换算为 bps。
func readOrderSnapshotBPS(raw datatypes.JSON, key string) int64 {
breakdown, ok := readOrderSnapshotBreakdown(raw)
if !ok {
return 0
}
if bps := readJSONNumber(breakdown[key+"_bps"]); bps > 0 {
return int64(math.Round(bps))
}
legacy := readJSONNumber(breakdown[key])
if legacy <= 0 {
return 0
}
return int64(math.Round(legacy * 10000))
}
// quantityUnits 把 M 数量转成百分之一 M 的整数单位,避免比例计算依赖浮点。
func quantityUnits(value float64) int64 {
if value <= 0 || math.IsNaN(value) || math.IsInf(value, 0) {
return 0
}
return int64(math.Round(value * 100))
}
// roundedDiv 对非负整数做四舍五入除法。
func roundedDiv(numerator, denominator int64) int64 {
if numerator <= 0 || denominator <= 0 {
return 0
}
return (numerator + denominator/2) / denominator
}
// proportionalCent 按 part/total 折算金额;total<=0 时保持旧行为取全额。
func proportionalCent(baseCent, partUnits, totalUnits int64) int64 {
if baseCent <= 0 {
return 0
}
if totalUnits <= 0 {
return baseCent
}
if partUnits <= 0 {
return 0
}
return roundedDiv(baseCent*partUnits, totalUnits)
}
func readJSONNumber(value any) float64 {
switch typed := value.(type) {
case float64:
return typed
case float32:
return float64(typed)
case int:
return float64(typed)
case int64:
return float64(typed)
case json.Number:
number, err := typed.Float64()
if err != nil {
return 0
}
return number
case string:
number, err := strconv.ParseFloat(typed, 64)
if err != nil {
return 0
}
return number
default:
return 0
}
}
func buildCheckout(order model.RentalOrder, initiatedBy uint64, status string, content string, evidenceURLS []string, consumableAmountCent int64, coinConsumedM float64, otherAmountCent int64, explicitDeductCent int64, useExplicitDeduct bool) (model.OrderCheckout, error) {
if consumableAmountCent < 0 || coinConsumedM < 0 || otherAmountCent < 0 || explicitDeductCent < 0 {
return model.OrderCheckout{}, ErrInvalidCheckoutAmount
}
deductAmountCent := otherAmountCent
if useExplicitDeduct {
deductAmountCent = explicitDeductCent
}
settlement := calculateCheckoutSettlement(order, consumableAmountCent, roundQuantity(coinConsumedM), deductAmountCent)
evidence, err := marshalStringList(evidenceURLS)
if err != nil {
return model.OrderCheckout{}, err
}
return model.OrderCheckout{
OrderID: order.ID,
InitiatedBy: initiatedBy,
Status: status,
RentAmountCent: settlement.ActualRentAmountCent,
OwnerRentAmountCent: settlement.OwnerRentIncomeCent,
PlatformFeeCent: settlement.PlatformFeeCent,
DepositAmountCent: order.DepositAmountCent,
PureCoinAmountCent: settlement.PureCoinAmountCent,
PureCoinDiscountCent: settlement.PureCoinDiscountCent,
PureCoinPayableCent: settlement.PureCoinPayableCent,
ConsumableAmountCent: consumableAmountCent,
CoinConsumedM: roundQuantity(coinConsumedM),
OtherAmountCent: otherAmountCent,
// 存用户申报的押金赔付(损坏等),打超由结算自动计入 shortfall/overshoot
DepositDeductAmountCent: deductAmountCent,
RenterRefundAmountCent: settlement.RenterRefundCent,
OwnerIncomeAmountCent: settlement.OwnerIncomeCent,
ShortfallCent: settlement.ShortfallCent,
OvershootAmountCent: settlement.OvershootAmountCent,
Content: content,
EvidenceURLS: evidence,
}, nil
}
func buildCheckoutSettlement(order model.RentalOrder, checkout *model.OrderCheckout) checkoutSettlement {
return calculateCheckoutSettlement(order, checkout.ConsumableAmountCent, checkout.CoinConsumedM, effectiveCheckoutDepositDeductCent(*checkout))
}
// effectiveCheckoutDepositDeductCent keeps legacy checkouts that only populated
// other_amount_cent from losing their deposit compensation at finalization.
func effectiveCheckoutDepositDeductCent(checkout model.OrderCheckout) int64 {
if checkout.DepositDeductAmountCent > 0 {
return checkout.DepositDeductAmountCent
}
return maxCent(checkout.OtherAmountCent, 0)
}
func orderConsumptionPrepaidCent(order model.RentalOrder) int64 {
return order.RentAmountCent
}
func calculateCheckoutSettlement(order model.RentalOrder, consumableAmountCent int64, coinConsumedM float64, depositDeductAmountCent int64) checkoutSettlement {
orderRentAmountCent := orderConsumptionPrepaidCent(order)
orderOwnerRentAmountCent := order.OwnerRentAmountCent
orderDepositAmountCent := order.DepositAmountCent
buyerCoinBasePriceCent := orderPureCoinOriginalAmountCent(order)
sellerCoinBasePriceCent := readOrderSnapshotCent(order.AccountSnapshot, "seller_coin_base_price")
if sellerCoinBasePriceCent <= 0 || sellerCoinBasePriceCent > orderOwnerRentAmountCent {
ownerExtraItemCent := minCent(orderExtraItemOriginalAmountCent(order), orderOwnerRentAmountCent)
sellerCoinBasePriceCent = maxCent(orderOwnerRentAmountCent-ownerExtraItemCent, 0)
}
prepaidConsumablePriceCent := orderExtraItemOriginalAmountCent(order)
prepaidOwnerConsumablePriceCent := maxCent(orderOwnerRentAmountCent-sellerCoinBasePriceCent, 0)
consumedM := roundQuantity(coinConsumedM)
totalCoinM := readOrderSnapshotCoinM(order.AccountSnapshot)
buyerRatioBPS := readOrderSnapshotBPS(order.AccountSnapshot, "buyer_ratio")
sellerRatioBPS := readOrderSnapshotBPS(order.AccountSnapshot, "seller_ratio")
consumedUnits := quantityUnits(consumedM)
totalCoinUnits := quantityUnits(totalCoinM)
// 币费:比例优先按 bps 整数换算;缺失 bps 时把旧比例换算成 bps。
// 无比例时回退到预收币基价按消耗比例折算,仍使用整数除法。
var usedBuyerCoinPriceCent, usedOwnerCoinPriceCent int64
if buyerRatioBPS > 0 && (totalCoinUnits > 0 || consumedUnits > 0) {
usedBuyerCoinPriceCent = roundedDiv(consumedUnits*1_000_000, buyerRatioBPS)
} else {
usedBuyerCoinPriceCent = proportionalCent(buyerCoinBasePriceCent, consumedUnits, totalCoinUnits)
}
if sellerRatioBPS > 0 && (totalCoinUnits > 0 || consumedUnits > 0) {
usedOwnerCoinPriceCent = roundedDiv(consumedUnits*1_000_000, sellerRatioBPS)
} else {
usedOwnerCoinPriceCent = proportionalCent(sellerCoinBasePriceCent, consumedUnits, totalCoinUnits)
}
coinOvershoot := totalCoinUnits > 0 && consumedUnits > totalCoinUnits
if !coinOvershoot {
// 未打超时,比例反推只能用于折算未用完部分,不能因取整超过订单快照。
if totalCoinUnits > 0 && consumedUnits >= totalCoinUnits {
usedBuyerCoinPriceCent = buyerCoinBasePriceCent
usedOwnerCoinPriceCent = sellerCoinBasePriceCent
} else {
usedBuyerCoinPriceCent = minCent(usedBuyerCoinPriceCent, buyerCoinBasePriceCent)
usedOwnerCoinPriceCent = minCent(usedOwnerCoinPriceCent, sellerCoinBasePriceCent)
}
}
// 消耗品按申报金额计价,允许超过预收;号主侧按预收消耗品占比外推,超额及无预收损耗全额归号主
usedBuyerConsumablePriceCent := maxCent(consumableAmountCent, 0)
var usedOwnerConsumablePriceCent int64
if prepaidConsumablePriceCent > 0 {
if usedBuyerConsumablePriceCent <= prepaidConsumablePriceCent {
usedOwnerConsumablePriceCent = roundedDiv(
prepaidOwnerConsumablePriceCent*usedBuyerConsumablePriceCent,
prepaidConsumablePriceCent,
)
usedOwnerConsumablePriceCent = minCent(usedOwnerConsumablePriceCent, prepaidOwnerConsumablePriceCent)
} else {
// 预收范围内按号主预收基准,超出预收部分作为后置损耗全额划归号主(平台不抽成)
excessCent := usedBuyerConsumablePriceCent - prepaidConsumablePriceCent
usedOwnerConsumablePriceCent = prepaidOwnerConsumablePriceCent + excessCent
}
} else {
// 原订单无预收消耗品,后置申报的消耗/损耗金额 100% 归号主,平台不抽成
usedOwnerConsumablePriceCent = usedBuyerConsumablePriceCent
}
pureCoinPlatformFeeCent := maxCent(usedBuyerCoinPriceCent-usedOwnerCoinPriceCent, 0)
pureCoinDiscountCent := rentergrowth.CalculateDiscountCent(usedBuyerCoinPriceCent, pureCoinPlatformFeeCent, effectiveRenterDiscountBps(order))
pureCoinPayableCent := maxCent(usedBuyerCoinPriceCent-pureCoinDiscountCent, 0)
actualBuyerRentCent := pureCoinPayableCent + usedBuyerConsumablePriceCent
actualOwnerRentCent := usedOwnerCoinPriceCent + usedOwnerConsumablePriceCent
if actualOwnerRentCent > actualBuyerRentCent {
actualOwnerRentCent = actualBuyerRentCent
}
// 打超:实际租客侧费用超出预收租金的部分,从押金扣
overshootCent := maxCent(actualBuyerRentCent-orderRentAmountCent, 0)
damageRequestCent := maxCent(depositDeductAmountCent, 0)
// 押金先覆盖损坏赔付,再覆盖打超;不足记 shortfall,禁止自动完结
damageAppliedCent := minCent(damageRequestCent, orderDepositAmountCent)
depositLeftAfterDamage := maxCent(orderDepositAmountCent-damageAppliedCent, 0)
overshootCoveredCent := minCent(overshootCent, depositLeftAfterDamage)
shortfallCent := maxCent(damageRequestCent-damageAppliedCent, 0) + maxCent(overshootCent-overshootCoveredCent, 0)
rentFromPrepaidCent := minCent(actualBuyerRentCent, orderRentAmountCent)
rentRefundCent := maxCent(orderRentAmountCent-rentFromPrepaidCent, 0)
depositUsedCent := damageAppliedCent + overshootCoveredCent
depositRefundCent := maxCent(orderDepositAmountCent-depositUsedCent, 0)
// 可完结时租客实际承担的租金向金额;有 shortfall 时仍展示完整应付便于协商
actualRentAmountCent := actualBuyerRentCent
ownerRentIncomeCent := actualOwnerRentCent
platformFeeCent := maxCent(actualRentAmountCent-ownerRentIncomeCent, 0)
depositCompensationCent := damageAppliedCent
return checkoutSettlement{
OwnerRentIncomeCent: ownerRentIncomeCent,
DepositCompensationCent: depositCompensationCent,
OwnerIncomeCent: ownerRentIncomeCent + depositCompensationCent,
RentRefundCent: rentRefundCent,
DepositRefundCent: depositRefundCent,
RenterRefundCent: rentRefundCent + depositRefundCent,
PlatformFeeCent: platformFeeCent,
ActualRentAmountCent: actualRentAmountCent,
PureCoinAmountCent: usedBuyerCoinPriceCent,
PureCoinDiscountCent: pureCoinDiscountCent,
PureCoinPayableCent: pureCoinPayableCent,
ExtraItemAmountCent: usedBuyerConsumablePriceCent,
CoinConsumedM: consumedM,
OvershootAmountCent: overshootCent,
ShortfallCent: shortfallCent,
}
}
func orderPureCoinOriginalAmountCent(order model.RentalOrder) int64 {
originalRentCent := effectiveRentOriginalAmountCent(order)
if order.PureCoinOriginalAmountCent > 0 {
return minCent(order.PureCoinOriginalAmountCent, originalRentCent)
}
if order.ExtraItemOriginalAmountCent > 0 {
return maxCent(originalRentCent-minCent(order.ExtraItemOriginalAmountCent, originalRentCent), 0)
}
pureCoinCent := readOrderSnapshotCent(order.AccountSnapshot, "buyer_coin_base_price")
if pureCoinCent > 0 && pureCoinCent <= originalRentCent {
return pureCoinCent
}
extraItemCent := readOrderSnapshotCent(order.AccountSnapshot, "consumable_price")
if extraItemCent > 0 && extraItemCent <= originalRentCent {
return originalRentCent - extraItemCent
}
return originalRentCent
}
func orderExtraItemOriginalAmountCent(order model.RentalOrder) int64 {
originalRentCent := effectiveRentOriginalAmountCent(order)
if order.ExtraItemOriginalAmountCent > 0 {
return minCent(order.ExtraItemOriginalAmountCent, originalRentCent)
}
pureCoinCent := orderPureCoinOriginalAmountCent(order)
return maxCent(originalRentCent-pureCoinCent, 0)
}
func readOrderSnapshotCoinM(raw datatypes.JSON) float64 {
if len(raw) == 0 {
return 0
}
var snapshot map[string]any
if err := json.Unmarshal(raw, &snapshot); err != nil {
return 0
}
return roundQuantity(readJSONNumber(snapshot["haf_coin_amount"]) / 1000000)
}
// coinConsumptionRatio 实际消耗相对发布量的比例,允许 >1 表示打超。
// publishedM<=0 时视为无快照,回退为全额使用(兼容旧单测/无币价场景)。
func coinConsumptionRatio(consumedM, publishedM float64) float64 {
if publishedM > 0 {
return maxRatio(roundQuantity(consumedM)/publishedM, 0)
}
return 1
}
func readOrderSnapshotAssetNumber(raw datatypes.JSON, key string) float64 {
if len(raw) == 0 {
return 0
}
var snapshot map[string]any
if err := json.Unmarshal(raw, &snapshot); err != nil {
return 0
}
assetSummary, ok := snapshot["asset_summary"].(map[string]any)
if !ok {
return readJSONNumber(snapshot[key])
}
if value, ok := assetSummary[key]; ok {
return readJSONNumber(value)
}
return readJSONNumber(snapshot[key])
}
// roundMoney 是订单历史链路的角精度兼容实现;新金额链路应直接使用整数分。
func roundMoney(value float64) float64 {
if value <= 0 || math.IsNaN(value) || math.IsInf(value, 0) {
return 0
}
return math.Round(value*10) / 10
}
func roundQuantity(value float64) float64 {
return math.Round(value*100) / 100
}
// minMoney 返回较小金额(角精度兼容)。
func minMoney(a float64, b float64) float64 {
if a < b {
return a
}
return b
}
// maxMoney 返回较大金额(角精度兼容)。
func maxMoney(a float64, b float64) float64 {
if a > b {
return a
}
return b
}
func minCent(a int64, b int64) int64 {
if a < b {
return a
}
return b
}
func maxCent(a int64, b int64) int64 {
if a > b {
return a
}
return b
}
func minRatio(a float64, b float64) float64 {
if a < b {
return a
}
return b
}
func maxRatio(a float64, b float64) float64 {
if a > b {
return a
}
return b
}