支持结账多轮协商与按实际消耗双比例结算
允许双方最多 6 轮修改结账方案;哈夫币按实际消耗与 buyer/seller 比例计价,打超从押金扣,押金不足禁止自动完结并引导人工争议。同步收紧结账明细展示密度。
This commit is contained in:
@@ -27,6 +27,8 @@ type checkoutSettlement struct {
|
||||
RenterRefundCent int64
|
||||
PlatformFeeCent int64
|
||||
ActualRentAmountCent int64
|
||||
OvershootAmountCent int64
|
||||
ShortfallCent int64
|
||||
}
|
||||
|
||||
func orderDurationHours(order model.RentalOrder) int {
|
||||
@@ -145,9 +147,6 @@ func buildCheckout(order model.RentalOrder, initiatedBy uint64, status string, c
|
||||
if useExplicitDeduct {
|
||||
deductAmountCent = explicitDeductCent
|
||||
}
|
||||
if deductAmountCent > order.DepositAmountCent {
|
||||
return model.OrderCheckout{}, ErrInvalidCheckoutAmount
|
||||
}
|
||||
settlement := calculateCheckoutSettlement(order, consumableAmountCent, roundQuantity(coinConsumedM), deductAmountCent)
|
||||
evidence, err := marshalStringList(evidenceURLS)
|
||||
if err != nil {
|
||||
@@ -164,9 +163,12 @@ func buildCheckout(order model.RentalOrder, initiatedBy uint64, status string, c
|
||||
ConsumableAmountCent: consumableAmountCent,
|
||||
CoinConsumedM: roundQuantity(coinConsumedM),
|
||||
OtherAmountCent: otherAmountCent,
|
||||
DepositDeductAmountCent: settlement.DepositCompensationCent,
|
||||
// 存用户申报的押金赔付(损坏等),打超由结算自动计入 shortfall/overshoot
|
||||
DepositDeductAmountCent: deductAmountCent,
|
||||
RenterRefundAmountCent: settlement.RenterRefundCent,
|
||||
OwnerIncomeAmountCent: settlement.OwnerIncomeCent,
|
||||
ShortfallCent: settlement.ShortfallCent,
|
||||
OvershootAmountCent: settlement.OvershootAmountCent,
|
||||
Content: content,
|
||||
EvidenceURLS: evidence,
|
||||
}, nil
|
||||
@@ -194,24 +196,61 @@ func calculateCheckoutSettlement(order model.RentalOrder, consumableAmountCent i
|
||||
prepaidConsumablePriceCent = maxCent(orderRentAmountCent-buyerCoinBasePriceCent, 0)
|
||||
}
|
||||
prepaidOwnerConsumablePriceCent := maxCent(orderOwnerRentAmountCent-sellerCoinBasePriceCent, 0)
|
||||
|
||||
consumedM := roundQuantity(coinConsumedM)
|
||||
totalCoinM := readOrderSnapshotCoinM(order.AccountSnapshot)
|
||||
coinUseRatio := 1.0
|
||||
if totalCoinM > 0 {
|
||||
coinUseRatio = minRatio(maxRatio(roundQuantity(coinConsumedM)/totalCoinM, 0), 1)
|
||||
buyerRatio := readOrderSnapshotPrice(order.AccountSnapshot, "buyer_ratio")
|
||||
sellerRatio := readOrderSnapshotPrice(order.AccountSnapshot, "seller_ratio")
|
||||
|
||||
// 币费:优先按各自比例 × 实际消耗;无比例时回退到预收币基价 × (消耗/发布量),允许 >1(打超)
|
||||
// 无发布量快照时保持旧行为:按全额使用(ratio=1)
|
||||
coinUseRatio := coinConsumptionRatio(consumedM, totalCoinM)
|
||||
var usedBuyerCoinPriceCent, usedOwnerCoinPriceCent int64
|
||||
if buyerRatio > 0 && (totalCoinM > 0 || consumedM > 0) {
|
||||
// 价(元) = 消耗M×100 / ratio;分 = 元×100
|
||||
usedBuyerCoinPriceCent = int64(math.Round(consumedM * 10000 / buyerRatio))
|
||||
} else {
|
||||
usedBuyerCoinPriceCent = int64(math.Round(float64(buyerCoinBasePriceCent) * coinUseRatio))
|
||||
}
|
||||
usedBuyerCoinPriceCent := int64(math.Round(float64(buyerCoinBasePriceCent) * coinUseRatio))
|
||||
usedOwnerCoinPriceCent := int64(math.Round(float64(sellerCoinBasePriceCent) * coinUseRatio))
|
||||
usedBuyerConsumablePriceCent := minCent(consumableAmountCent, prepaidConsumablePriceCent)
|
||||
consumableUseRatio := 1.0
|
||||
if sellerRatio > 0 && (totalCoinM > 0 || consumedM > 0) {
|
||||
usedOwnerCoinPriceCent = int64(math.Round(consumedM * 10000 / sellerRatio))
|
||||
} else {
|
||||
usedOwnerCoinPriceCent = int64(math.Round(float64(sellerCoinBasePriceCent) * coinUseRatio))
|
||||
}
|
||||
|
||||
// 消耗品按申报金额计价,允许超过预收;号主侧按预收消耗品占比线性外推
|
||||
usedBuyerConsumablePriceCent := maxCent(consumableAmountCent, 0)
|
||||
consumableUseRatio := 0.0
|
||||
if prepaidConsumablePriceCent > 0 {
|
||||
consumableUseRatio = minRatio(maxRatio(float64(usedBuyerConsumablePriceCent)/float64(prepaidConsumablePriceCent), 0), 1)
|
||||
consumableUseRatio = maxRatio(float64(usedBuyerConsumablePriceCent)/float64(prepaidConsumablePriceCent), 0)
|
||||
}
|
||||
usedOwnerConsumablePriceCent := int64(math.Round(float64(prepaidOwnerConsumablePriceCent) * consumableUseRatio))
|
||||
actualRentAmountCent := minCent(usedBuyerCoinPriceCent+usedBuyerConsumablePriceCent, orderRentAmountCent)
|
||||
ownerRentIncomeCent := minCent(usedOwnerCoinPriceCent+usedOwnerConsumablePriceCent, orderOwnerRentAmountCent)
|
||||
depositCompensationCent := minCent(depositDeductAmountCent, orderDepositAmountCent)
|
||||
rentRefundCent := maxCent(orderRentAmountCent-actualRentAmountCent, 0)
|
||||
depositRefundCent := maxCent(orderDepositAmountCent-depositCompensationCent, 0)
|
||||
|
||||
actualBuyerRentCent := usedBuyerCoinPriceCent + 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,
|
||||
@@ -220,8 +259,10 @@ func calculateCheckoutSettlement(order model.RentalOrder, consumableAmountCent i
|
||||
RentRefundCent: rentRefundCent,
|
||||
DepositRefundCent: depositRefundCent,
|
||||
RenterRefundCent: rentRefundCent + depositRefundCent,
|
||||
PlatformFeeCent: maxCent(actualRentAmountCent-ownerRentIncomeCent, 0),
|
||||
PlatformFeeCent: platformFeeCent,
|
||||
ActualRentAmountCent: actualRentAmountCent,
|
||||
OvershootAmountCent: overshootCent,
|
||||
ShortfallCent: shortfallCent,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,6 +277,15 @@ func readOrderSnapshotCoinM(raw datatypes.JSON) float64 {
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user