修复结账金额和后台类型
This commit is contained in:
@@ -40,6 +40,17 @@ type orderPricing struct {
|
|||||||
PlatformFee float64
|
PlatformFee float64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type checkoutSettlement struct {
|
||||||
|
OwnerRentIncome float64
|
||||||
|
DepositCompensation float64
|
||||||
|
OwnerIncome float64
|
||||||
|
RentRefund float64
|
||||||
|
DepositRefund float64
|
||||||
|
RenterRefund float64
|
||||||
|
PlatformFee float64
|
||||||
|
ActualRentAmount float64
|
||||||
|
}
|
||||||
|
|
||||||
func orderDurationHours(order model.RentalOrder) int {
|
func orderDurationHours(order model.RentalOrder) int {
|
||||||
if order.EstimatedDurationHours > 0 {
|
if order.EstimatedDurationHours > 0 {
|
||||||
return order.EstimatedDurationHours
|
return order.EstimatedDurationHours
|
||||||
@@ -68,14 +79,21 @@ func buildOrderPricing(listing model.RentalListing, account model.GameAccount) o
|
|||||||
}
|
}
|
||||||
|
|
||||||
func readSnapshotPrice(raw datatypes.JSON, key string) float64 {
|
func readSnapshotPrice(raw datatypes.JSON, key string) float64 {
|
||||||
|
return readOrderSnapshotPrice(raw, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func readOrderSnapshotPrice(raw datatypes.JSON, key string) float64 {
|
||||||
if len(raw) == 0 {
|
if len(raw) == 0 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
var summary map[string]any
|
var snapshot map[string]any
|
||||||
if err := json.Unmarshal(raw, &summary); err != nil {
|
if err := json.Unmarshal(raw, &snapshot); err != nil {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
breakdown, ok := summary["price_breakdown"].(map[string]any)
|
if assetSummary, ok := snapshot["asset_summary"].(map[string]any); ok {
|
||||||
|
snapshot = assetSummary
|
||||||
|
}
|
||||||
|
breakdown, ok := snapshot["price_breakdown"].(map[string]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -927,7 +945,8 @@ func (r *Repository) finalizeCheckout(tx *gorm.DB, order *model.RentalOrder, che
|
|||||||
listing.InTransaction = false
|
listing.InTransaction = false
|
||||||
account.Status = "published"
|
account.Status = "published"
|
||||||
orderID := order.ID
|
orderID := order.ID
|
||||||
if err := wallet.AppendEntries(tx,
|
settlement := buildCheckoutSettlement(*order, checkout)
|
||||||
|
entries := []wallet.Entry{
|
||||||
wallet.Entry{
|
wallet.Entry{
|
||||||
UserID: order.RenterID,
|
UserID: order.RenterID,
|
||||||
OrderID: &orderID,
|
OrderID: &orderID,
|
||||||
@@ -938,29 +957,63 @@ func (r *Repository) finalizeCheckout(tx *gorm.DB, order *model.RentalOrder, che
|
|||||||
BizNo: order.OrderNo,
|
BizNo: order.OrderNo,
|
||||||
Remark: "订单结账释放冻结金额",
|
Remark: "订单结账释放冻结金额",
|
||||||
},
|
},
|
||||||
wallet.Entry{
|
}
|
||||||
|
if settlement.OwnerRentIncome > 0 {
|
||||||
|
entries = append(entries, wallet.Entry{
|
||||||
UserID: order.OwnerID,
|
UserID: order.OwnerID,
|
||||||
OrderID: &orderID,
|
OrderID: &orderID,
|
||||||
Direction: "in",
|
Direction: "in",
|
||||||
Amount: checkout.OwnerIncomeAmount,
|
Amount: settlement.OwnerRentIncome,
|
||||||
BalanceType: "available",
|
BalanceType: "available",
|
||||||
BizType: "owner_income",
|
BizType: "owner_income",
|
||||||
BizNo: order.OrderNo,
|
BizNo: order.OrderNo,
|
||||||
Remark: "订单结账收入",
|
Remark: "订单结账租金收入",
|
||||||
},
|
})
|
||||||
wallet.Entry{
|
}
|
||||||
|
if settlement.DepositCompensation > 0 {
|
||||||
|
entries = append(entries, wallet.Entry{
|
||||||
|
UserID: order.OwnerID,
|
||||||
|
OrderID: &orderID,
|
||||||
|
Direction: "in",
|
||||||
|
Amount: settlement.DepositCompensation,
|
||||||
|
BalanceType: "available",
|
||||||
|
BizType: "deposit_compensation",
|
||||||
|
BizNo: order.OrderNo,
|
||||||
|
Remark: "订单结账押金赔付",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if settlement.RentRefund > 0 {
|
||||||
|
entries = append(entries, wallet.Entry{
|
||||||
UserID: order.RenterID,
|
UserID: order.RenterID,
|
||||||
OrderID: &orderID,
|
OrderID: &orderID,
|
||||||
Direction: "in",
|
Direction: "in",
|
||||||
Amount: checkout.RenterRefundAmount,
|
Amount: settlement.RentRefund,
|
||||||
|
BalanceType: "available",
|
||||||
|
BizType: "rent_refund",
|
||||||
|
BizNo: order.OrderNo,
|
||||||
|
Remark: "订单结账退回未使用租金",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if settlement.DepositRefund > 0 {
|
||||||
|
entries = append(entries, wallet.Entry{
|
||||||
|
UserID: order.RenterID,
|
||||||
|
OrderID: &orderID,
|
||||||
|
Direction: "in",
|
||||||
|
Amount: settlement.DepositRefund,
|
||||||
BalanceType: "available",
|
BalanceType: "available",
|
||||||
BizType: "deposit_release",
|
BizType: "deposit_release",
|
||||||
BizNo: order.OrderNo,
|
BizNo: order.OrderNo,
|
||||||
Remark: "订单结账退回押金",
|
Remark: "订单结账退回押金",
|
||||||
},
|
})
|
||||||
); err != nil {
|
}
|
||||||
|
if err := wallet.AppendEntries(tx, entries...); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
checkout.RentAmount = settlement.ActualRentAmount
|
||||||
|
checkout.OwnerRentAmount = settlement.OwnerRentIncome
|
||||||
|
checkout.PlatformFee = settlement.PlatformFee
|
||||||
|
checkout.RenterRefundAmount = settlement.RenterRefund
|
||||||
|
checkout.OwnerIncomeAmount = settlement.OwnerIncome
|
||||||
if err := notification.Append(tx,
|
if err := notification.Append(tx,
|
||||||
notification.Entry{
|
notification.Entry{
|
||||||
UserID: order.RenterID,
|
UserID: order.RenterID,
|
||||||
@@ -1049,7 +1102,7 @@ func buildCheckout(order model.RentalOrder, initiatedBy uint64, status string, c
|
|||||||
if deductAmount > order.DepositAmount {
|
if deductAmount > order.DepositAmount {
|
||||||
return model.OrderCheckout{}, ErrInvalidCheckoutAmount
|
return model.OrderCheckout{}, ErrInvalidCheckoutAmount
|
||||||
}
|
}
|
||||||
renterRefund := order.DepositAmount - deductAmount
|
settlement := calculateCheckoutSettlement(order, consumableAmount, roundQuantity(coinConsumedM), deductAmount)
|
||||||
evidence, err := marshalStringList(evidenceURLS)
|
evidence, err := marshalStringList(evidenceURLS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return model.OrderCheckout{}, err
|
return model.OrderCheckout{}, err
|
||||||
@@ -1058,21 +1111,80 @@ func buildCheckout(order model.RentalOrder, initiatedBy uint64, status string, c
|
|||||||
OrderID: order.ID,
|
OrderID: order.ID,
|
||||||
InitiatedBy: initiatedBy,
|
InitiatedBy: initiatedBy,
|
||||||
Status: status,
|
Status: status,
|
||||||
RentAmount: order.RentAmount,
|
RentAmount: settlement.ActualRentAmount,
|
||||||
OwnerRentAmount: order.OwnerRentAmount,
|
OwnerRentAmount: settlement.OwnerRentIncome,
|
||||||
PlatformFee: order.PlatformFee,
|
PlatformFee: settlement.PlatformFee,
|
||||||
DepositAmount: order.DepositAmount,
|
DepositAmount: order.DepositAmount,
|
||||||
ConsumableAmount: consumableAmount,
|
ConsumableAmount: consumableAmount,
|
||||||
CoinConsumedM: roundQuantity(coinConsumedM),
|
CoinConsumedM: roundQuantity(coinConsumedM),
|
||||||
OtherAmount: otherAmount,
|
OtherAmount: otherAmount,
|
||||||
DepositDeductAmount: roundMoney(deductAmount),
|
DepositDeductAmount: roundMoney(deductAmount),
|
||||||
RenterRefundAmount: roundMoney(renterRefund),
|
RenterRefundAmount: settlement.RenterRefund,
|
||||||
OwnerIncomeAmount: roundMoney(order.OwnerRentAmount + deductAmount),
|
OwnerIncomeAmount: settlement.OwnerIncome,
|
||||||
Content: content,
|
Content: content,
|
||||||
EvidenceURLS: evidence,
|
EvidenceURLS: evidence,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildCheckoutSettlement(order model.RentalOrder, checkout *model.OrderCheckout) checkoutSettlement {
|
||||||
|
return calculateCheckoutSettlement(order, checkout.ConsumableAmount, checkout.CoinConsumedM, checkout.DepositDeductAmount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func calculateCheckoutSettlement(order model.RentalOrder, consumableAmount float64, coinConsumedM float64, depositDeductAmount float64) checkoutSettlement {
|
||||||
|
buyerCoinBasePrice := readOrderSnapshotPrice(order.AccountSnapshot, "buyer_coin_base_price")
|
||||||
|
if buyerCoinBasePrice <= 0 || buyerCoinBasePrice > order.RentAmount {
|
||||||
|
buyerCoinBasePrice = order.RentAmount
|
||||||
|
}
|
||||||
|
sellerCoinBasePrice := readOrderSnapshotPrice(order.AccountSnapshot, "seller_coin_base_price")
|
||||||
|
if sellerCoinBasePrice <= 0 || sellerCoinBasePrice > order.OwnerRentAmount {
|
||||||
|
sellerCoinBasePrice = order.OwnerRentAmount
|
||||||
|
}
|
||||||
|
prepaidConsumablePrice := readOrderSnapshotPrice(order.AccountSnapshot, "consumable_price")
|
||||||
|
if prepaidConsumablePrice <= 0 || prepaidConsumablePrice > order.RentAmount-buyerCoinBasePrice {
|
||||||
|
prepaidConsumablePrice = maxMoney(order.RentAmount-buyerCoinBasePrice, 0)
|
||||||
|
}
|
||||||
|
prepaidOwnerConsumablePrice := maxMoney(order.OwnerRentAmount-sellerCoinBasePrice, 0)
|
||||||
|
totalCoinM := readOrderSnapshotCoinM(order.AccountSnapshot)
|
||||||
|
coinUseRatio := 1.0
|
||||||
|
if totalCoinM > 0 {
|
||||||
|
coinUseRatio = minRatio(maxRatio(roundQuantity(coinConsumedM)/totalCoinM, 0), 1)
|
||||||
|
}
|
||||||
|
usedBuyerCoinPrice := roundMoney(buyerCoinBasePrice * coinUseRatio)
|
||||||
|
usedOwnerCoinPrice := roundMoney(sellerCoinBasePrice * coinUseRatio)
|
||||||
|
usedBuyerConsumablePrice := minMoney(roundMoney(consumableAmount), prepaidConsumablePrice)
|
||||||
|
consumableUseRatio := 1.0
|
||||||
|
if prepaidConsumablePrice > 0 {
|
||||||
|
consumableUseRatio = minRatio(maxRatio(usedBuyerConsumablePrice/prepaidConsumablePrice, 0), 1)
|
||||||
|
}
|
||||||
|
usedOwnerConsumablePrice := roundMoney(prepaidOwnerConsumablePrice * consumableUseRatio)
|
||||||
|
actualRentAmount := minMoney(roundMoney(usedBuyerCoinPrice+usedBuyerConsumablePrice), order.RentAmount)
|
||||||
|
ownerRentIncome := minMoney(roundMoney(usedOwnerCoinPrice+usedOwnerConsumablePrice), order.OwnerRentAmount)
|
||||||
|
depositCompensation := minMoney(roundMoney(depositDeductAmount), order.DepositAmount)
|
||||||
|
rentRefund := maxMoney(order.RentAmount-actualRentAmount, 0)
|
||||||
|
depositRefund := maxMoney(order.DepositAmount-depositCompensation, 0)
|
||||||
|
return checkoutSettlement{
|
||||||
|
OwnerRentIncome: ownerRentIncome,
|
||||||
|
DepositCompensation: depositCompensation,
|
||||||
|
OwnerIncome: roundMoney(ownerRentIncome + depositCompensation),
|
||||||
|
RentRefund: rentRefund,
|
||||||
|
DepositRefund: depositRefund,
|
||||||
|
RenterRefund: roundMoney(rentRefund + depositRefund),
|
||||||
|
PlatformFee: maxMoney(actualRentAmount-ownerRentIncome, 0),
|
||||||
|
ActualRentAmount: actualRentAmount,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
func marshalStringList(items []string) (datatypes.JSON, error) {
|
func marshalStringList(items []string) (datatypes.JSON, error) {
|
||||||
if items == nil {
|
if items == nil {
|
||||||
items = []string{}
|
items = []string{}
|
||||||
@@ -1100,6 +1212,34 @@ func roundQuantity(value float64) float64 {
|
|||||||
return math.Round(value*100) / 100
|
return math.Round(value*100) / 100
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func minMoney(a float64, b float64) float64 {
|
||||||
|
if a < b {
|
||||||
|
return roundMoney(a)
|
||||||
|
}
|
||||||
|
return roundMoney(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func maxMoney(a float64, b float64) float64 {
|
||||||
|
if a > b {
|
||||||
|
return roundMoney(a)
|
||||||
|
}
|
||||||
|
return roundMoney(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
|
||||||
|
}
|
||||||
|
|
||||||
func hasOpenCheckout(tx *gorm.DB, orderID uint64) (bool, error) {
|
func hasOpenCheckout(tx *gorm.DB, orderID uint64) (bool, error) {
|
||||||
var count int64
|
var count int64
|
||||||
err := tx.Model(&model.OrderCheckout{}).
|
err := tx.Model(&model.OrderCheckout{}).
|
||||||
@@ -1288,27 +1428,33 @@ func applyCheckoutPriceView(dto *CheckoutDTO, order model.RentalOrder, userID ui
|
|||||||
if dto == nil {
|
if dto == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
ownerAmount := 0.0
|
||||||
|
if dto.OwnerRentAmount != nil {
|
||||||
|
ownerAmount = *dto.OwnerRentAmount
|
||||||
|
}
|
||||||
|
ownerIncomeAmount := 0.0
|
||||||
|
if dto.OwnerIncomeAmount != nil {
|
||||||
|
ownerIncomeAmount = *dto.OwnerIncomeAmount
|
||||||
|
}
|
||||||
|
rentAmount := 0.0
|
||||||
|
if dto.RentAmount != nil {
|
||||||
|
rentAmount = *dto.RentAmount
|
||||||
|
}
|
||||||
|
renterRefundAmount := 0.0
|
||||||
|
if dto.RenterRefundAmount != nil {
|
||||||
|
renterRefundAmount = *dto.RenterRefundAmount
|
||||||
|
}
|
||||||
dto.PlatformFee = nil
|
dto.PlatformFee = nil
|
||||||
dto.RenterRefundAmount = nil
|
dto.RenterRefundAmount = nil
|
||||||
dto.OwnerIncomeAmount = nil
|
dto.OwnerIncomeAmount = nil
|
||||||
switch {
|
switch {
|
||||||
case userID == order.OwnerID:
|
case userID == order.OwnerID:
|
||||||
ownerAmount := order.OwnerRentAmount
|
|
||||||
if ownerAmount <= 0 {
|
|
||||||
ownerAmount = order.RentAmount
|
|
||||||
}
|
|
||||||
ownerIncomeAmount := dto.DepositDeductAmount + ownerAmount
|
|
||||||
dto.PriceRole = "owner"
|
dto.PriceRole = "owner"
|
||||||
dto.DisplayAmount = ownerAmount
|
dto.DisplayAmount = ownerAmount
|
||||||
dto.RentAmount = nil
|
dto.RentAmount = nil
|
||||||
dto.OwnerRentAmount = &ownerAmount
|
dto.OwnerRentAmount = &ownerAmount
|
||||||
dto.OwnerIncomeAmount = &ownerIncomeAmount
|
dto.OwnerIncomeAmount = &ownerIncomeAmount
|
||||||
case userID == order.RenterID:
|
case userID == order.RenterID:
|
||||||
rentAmount := order.RentAmount
|
|
||||||
renterRefundAmount := order.DepositAmount - dto.DepositDeductAmount
|
|
||||||
if renterRefundAmount < 0 {
|
|
||||||
renterRefundAmount = 0
|
|
||||||
}
|
|
||||||
dto.PriceRole = "renter"
|
dto.PriceRole = "renter"
|
||||||
dto.DisplayAmount = rentAmount
|
dto.DisplayAmount = rentAmount
|
||||||
dto.RentAmount = &rentAmount
|
dto.RentAmount = &rentAmount
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
package order
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"hfb_sys/backend/internal/model"
|
||||||
|
|
||||||
|
"gorm.io/datatypes"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCalculateCheckoutSettlementRefundsUnusedRent(t *testing.T) {
|
||||||
|
order := model.RentalOrder{
|
||||||
|
RentAmount: 383,
|
||||||
|
OwnerRentAmount: 353,
|
||||||
|
DepositAmount: 150,
|
||||||
|
AccountSnapshot: datatypes.JSON([]byte(`{
|
||||||
|
"haf_coin_amount": 100000000,
|
||||||
|
"asset_summary": {
|
||||||
|
"price_breakdown": {
|
||||||
|
"buyer_coin_base_price": 263,
|
||||||
|
"seller_coin_base_price": 233,
|
||||||
|
"consumable_price": 120
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`)),
|
||||||
|
}
|
||||||
|
|
||||||
|
settlement := calculateCheckoutSettlement(order, 7, 90, 0)
|
||||||
|
|
||||||
|
if settlement.ActualRentAmount != 244 {
|
||||||
|
t.Fatalf("ActualRentAmount = %.2f, want 244.00", settlement.ActualRentAmount)
|
||||||
|
}
|
||||||
|
if settlement.OwnerRentIncome != 217 {
|
||||||
|
t.Fatalf("OwnerRentIncome = %.2f, want 217.00", settlement.OwnerRentIncome)
|
||||||
|
}
|
||||||
|
if settlement.PlatformFee != 27 {
|
||||||
|
t.Fatalf("PlatformFee = %.2f, want 27.00", settlement.PlatformFee)
|
||||||
|
}
|
||||||
|
if settlement.RentRefund != 139 {
|
||||||
|
t.Fatalf("RentRefund = %.2f, want 139.00", settlement.RentRefund)
|
||||||
|
}
|
||||||
|
if settlement.DepositRefund != 150 {
|
||||||
|
t.Fatalf("DepositRefund = %.2f, want 150.00", settlement.DepositRefund)
|
||||||
|
}
|
||||||
|
if settlement.RenterRefund != 289 {
|
||||||
|
t.Fatalf("RenterRefund = %.2f, want 289.00", settlement.RenterRefund)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCalculateCheckoutSettlementUsesBuyerAndSellerRatiosSeparately(t *testing.T) {
|
||||||
|
order := model.RentalOrder{
|
||||||
|
RentAmount: 383,
|
||||||
|
OwnerRentAmount: 353,
|
||||||
|
DepositAmount: 150,
|
||||||
|
AccountSnapshot: datatypes.JSON([]byte(`{
|
||||||
|
"haf_coin_amount": 100000000,
|
||||||
|
"asset_summary": {
|
||||||
|
"price_breakdown": {
|
||||||
|
"buyer_coin_base_price": 263,
|
||||||
|
"seller_coin_base_price": 233,
|
||||||
|
"consumable_price": 120
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`)),
|
||||||
|
}
|
||||||
|
|
||||||
|
settlement := calculateCheckoutSettlement(order, 0, 50, 0)
|
||||||
|
|
||||||
|
if settlement.ActualRentAmount != 132 {
|
||||||
|
t.Fatalf("租客侧实际租金 = %.2f, want 132.00", settlement.ActualRentAmount)
|
||||||
|
}
|
||||||
|
if settlement.OwnerRentIncome != 117 {
|
||||||
|
t.Fatalf("卖家侧租金收入 = %.2f, want 117.00", settlement.OwnerRentIncome)
|
||||||
|
}
|
||||||
|
if settlement.PlatformFee != 15 {
|
||||||
|
t.Fatalf("平台差价 = %.2f, want 15.00", settlement.PlatformFee)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCalculateCheckoutSettlementAddsDepositCompensation(t *testing.T) {
|
||||||
|
order := model.RentalOrder{
|
||||||
|
RentAmount: 383,
|
||||||
|
OwnerRentAmount: 353,
|
||||||
|
DepositAmount: 150,
|
||||||
|
AccountSnapshot: datatypes.JSON([]byte(`{
|
||||||
|
"haf_coin_amount": 100000000,
|
||||||
|
"asset_summary": {
|
||||||
|
"price_breakdown": {
|
||||||
|
"buyer_coin_base_price": 263,
|
||||||
|
"seller_coin_base_price": 233,
|
||||||
|
"consumable_price": 120
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`)),
|
||||||
|
}
|
||||||
|
|
||||||
|
settlement := calculateCheckoutSettlement(order, 120, 100, 30)
|
||||||
|
|
||||||
|
if settlement.ActualRentAmount != 383 {
|
||||||
|
t.Fatalf("ActualRentAmount = %.2f, want 383.00", settlement.ActualRentAmount)
|
||||||
|
}
|
||||||
|
if settlement.OwnerRentIncome != 353 {
|
||||||
|
t.Fatalf("OwnerRentIncome = %.2f, want 353.00", settlement.OwnerRentIncome)
|
||||||
|
}
|
||||||
|
if settlement.DepositCompensation != 30 {
|
||||||
|
t.Fatalf("DepositCompensation = %.2f, want 30.00", settlement.DepositCompensation)
|
||||||
|
}
|
||||||
|
if settlement.OwnerIncome != 383 {
|
||||||
|
t.Fatalf("OwnerIncome = %.2f, want 383.00", settlement.OwnerIncome)
|
||||||
|
}
|
||||||
|
if settlement.RenterRefund != 120 {
|
||||||
|
t.Fatalf("RenterRefund = %.2f, want 120.00", settlement.RenterRefund)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import { ref, type Ref } from 'vue'
|
|||||||
interface AdminQueryOptions<T> {
|
interface AdminQueryOptions<T> {
|
||||||
fetchFn: () => Promise<T>
|
fetchFn: () => Promise<T>
|
||||||
immediate?: boolean
|
immediate?: boolean
|
||||||
|
initialData?: T
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AdminQueryResult<T> {
|
interface AdminQueryResult<T> {
|
||||||
@@ -15,7 +16,7 @@ interface AdminQueryResult<T> {
|
|||||||
export function useAdminQuery<T>(options: AdminQueryOptions<T>): AdminQueryResult<T> {
|
export function useAdminQuery<T>(options: AdminQueryOptions<T>): AdminQueryResult<T> {
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const error = ref<string | null>(null)
|
const error = ref<string | null>(null)
|
||||||
const data = ref<T>() as Ref<T>
|
const data = ref<T>(options.initialData as T) as Ref<T>
|
||||||
|
|
||||||
async function load() {
|
async function load() {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
|
|||||||
@@ -550,7 +550,7 @@ function linesToList(value: string) {
|
|||||||
<div class="checkout-resource-panel panel-action">
|
<div class="checkout-resource-panel panel-action">
|
||||||
<div class="checkout-resource-head">
|
<div class="checkout-resource-head">
|
||||||
<strong>额外消耗品</strong>
|
<strong>额外消耗品</strong>
|
||||||
<span>金额合计:¥{{ money(resourceChargeAmount) }}</span>
|
<span>已用金额:¥{{ money(resourceChargeAmount) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<el-empty v-if="checkoutResources.length === 0" description="订单快照中暂无额外消耗品" />
|
<el-empty v-if="checkoutResources.length === 0" description="订单快照中暂无额外消耗品" />
|
||||||
<div v-for="item in checkoutResources" v-else :key="item.key" class="checkout-resource-row">
|
<div v-for="item in checkoutResources" v-else :key="item.key" class="checkout-resource-row">
|
||||||
@@ -573,7 +573,7 @@ function linesToList(value: string) {
|
|||||||
<el-input-number v-model="checkoutForm.coin_consumed_m" class="full-control" :min="0" :max="snapshotHafCoinM" :precision="2" controls-position="right" />
|
<el-input-number v-model="checkoutForm.coin_consumed_m" class="full-control" :min="0" :max="snapshotHafCoinM" :precision="2" controls-position="right" />
|
||||||
<span class="field-hint">订单快照 {{ quantity(snapshotHafCoinM) }}M,预计剩余 {{ quantity(remainingHafCoinM) }}M</span>
|
<span class="field-hint">订单快照 {{ quantity(snapshotHafCoinM) }}M,预计剩余 {{ quantity(remainingHafCoinM) }}M</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="其他扣款(元)">
|
<el-form-item label="其他押金扣款(元)">
|
||||||
<el-input-number v-model="checkoutForm.other_amount" class="full-control" :min="0" :precision="0" controls-position="right" />
|
<el-input-number v-model="checkoutForm.other_amount" class="full-control" :min="0" :precision="0" controls-position="right" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
@@ -589,10 +589,10 @@ function linesToList(value: string) {
|
|||||||
|
|
||||||
<div v-if="order && order.checkout && ['pending_checkout_confirm', 'pending_checkout_accept'].includes(order.status)" class="order-panel">
|
<div v-if="order && order.checkout && ['pending_checkout_confirm', 'pending_checkout_accept'].includes(order.status)" class="order-panel">
|
||||||
<h2>结账明细</h2>
|
<h2>结账明细</h2>
|
||||||
<p>{{ orderAmountLabel }}:¥{{ money(order.checkout.display_amount) }},押金:¥{{ money(order.checkout.deposit_amount) }}</p>
|
<p>实际结算租金:¥{{ money(order.checkout.display_amount) }},预收押金:¥{{ money(order.checkout.deposit_amount) }}</p>
|
||||||
<p>额外消耗品金额:¥{{ money(order.checkout.consumable_amount) }},押金扣除:¥{{ money(order.checkout.deposit_deduct_amount) }}</p>
|
<p>额外消耗品已用:¥{{ money(order.checkout.consumable_amount) }},押金赔付扣除:¥{{ money(order.checkout.deposit_deduct_amount) }}</p>
|
||||||
<p v-if="isRenter">退还租客:¥{{ money(order.checkout.renter_refund_amount) }}</p>
|
<p v-if="isRenter">退还租客(未使用租金 + 剩余押金):¥{{ money(order.checkout.renter_refund_amount) }}</p>
|
||||||
<p v-if="isOwner">号主收入:¥{{ money(order.checkout.owner_income_amount) }}</p>
|
<p v-if="isOwner">号主最终收入(租金 + 押金赔付):¥{{ money(order.checkout.owner_income_amount) }}</p>
|
||||||
<p v-if="order.checkout.content">说明:{{ order.checkout.content }}</p>
|
<p v-if="order.checkout.content">说明:{{ order.checkout.content }}</p>
|
||||||
<p v-if="order.checkout.owner_adjustment_reason">修正原因:{{ order.checkout.owner_adjustment_reason }}</p>
|
<p v-if="order.checkout.owner_adjustment_reason">修正原因:{{ order.checkout.owner_adjustment_reason }}</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -604,13 +604,13 @@ function linesToList(value: string) {
|
|||||||
|
|
||||||
<h2 class="panel-action">修改结账</h2>
|
<h2 class="panel-action">修改结账</h2>
|
||||||
<el-form class="form-grid" label-position="top">
|
<el-form class="form-grid" label-position="top">
|
||||||
<el-form-item label="额外消耗品金额(已含租金)">
|
<el-form-item label="额外消耗品已用金额">
|
||||||
<el-input-number v-model="counterForm.consumable_amount" class="full-control" :min="0" :precision="0" controls-position="right" />
|
<el-input-number v-model="counterForm.consumable_amount" class="full-control" :min="0" :precision="0" controls-position="right" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="消耗哈夫币(M)">
|
<el-form-item label="消耗哈夫币(M)">
|
||||||
<el-input-number v-model="counterForm.coin_consumed_m" class="full-control" :min="0" :precision="2" controls-position="right" />
|
<el-input-number v-model="counterForm.coin_consumed_m" class="full-control" :min="0" :precision="2" controls-position="right" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="其他扣款(元)">
|
<el-form-item label="其他押金扣款(元)">
|
||||||
<el-input-number v-model="counterForm.other_amount" class="full-control" :min="0" :precision="0" controls-position="right" />
|
<el-input-number v-model="counterForm.other_amount" class="full-control" :min="0" :precision="0" controls-position="right" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="押金扣除(元)">
|
<el-form-item label="押金扣除(元)">
|
||||||
|
|||||||
@@ -17,8 +17,9 @@ const filters = reactive<AdminListingQuery>({
|
|||||||
limit: 200,
|
limit: 200,
|
||||||
})
|
})
|
||||||
|
|
||||||
const { loading, data: listings, load: loadListings } = useAdminTable<Listing>({
|
const { loading, data: listings, load: loadListings } = useAdminTable<Listing[]>({
|
||||||
fetchFn: () => fetchAdminListings(filters),
|
fetchFn: () => fetchAdminListings(filters),
|
||||||
|
initialData: [],
|
||||||
})
|
})
|
||||||
|
|
||||||
const publishedCount = computed(() => listings.value.filter((item) => item.status === 'published').length)
|
const publishedCount = computed(() => listings.value.filter((item) => item.status === 'published').length)
|
||||||
|
|||||||
@@ -8,8 +8,9 @@ import { formatDateTime } from '@/utils/time'
|
|||||||
|
|
||||||
const status = ref('')
|
const status = ref('')
|
||||||
|
|
||||||
const { loading, data: orders, load: loadOrders } = useAdminTable<Order>({
|
const { loading, data: orders, load: loadOrders } = useAdminTable<Order[]>({
|
||||||
fetchFn: fetchAdminOrders,
|
fetchFn: fetchAdminOrders,
|
||||||
|
initialData: [],
|
||||||
})
|
})
|
||||||
|
|
||||||
const filteredOrders = computed(() => {
|
const filteredOrders = computed(() => {
|
||||||
|
|||||||
@@ -792,12 +792,12 @@ function getStatusTagType(status: string) {
|
|||||||
<section v-if="order.checkout && ['pending_checkout_confirm', 'pending_checkout_accept'].includes(order.status)" class="card-section">
|
<section v-if="order.checkout && ['pending_checkout_confirm', 'pending_checkout_accept'].includes(order.status)" class="card-section">
|
||||||
<h3 class="section-title">结账结算账单</h3>
|
<h3 class="section-title">结账结算账单</h3>
|
||||||
<van-cell-group inset :border="false">
|
<van-cell-group inset :border="false">
|
||||||
<van-cell :title="orderAmountLabel" :value="`¥${money(order.checkout.display_amount)}`" />
|
<van-cell title="实际结算租金" :value="`¥${money(order.checkout.display_amount)}`" />
|
||||||
<van-cell title="押金总额" :value="`¥${money(order.checkout.deposit_amount)}`" />
|
<van-cell title="押金总额" :value="`¥${money(order.checkout.deposit_amount)}`" />
|
||||||
<van-cell title="额外消耗品金额" :value="`¥${money(order.checkout.consumable_amount)}`" />
|
<van-cell title="额外消耗品已用" :value="`¥${money(order.checkout.consumable_amount)}`" />
|
||||||
<van-cell title="押金扣除" :value="`¥${money(order.checkout.deposit_deduct_amount)}`" value-class="red-text" />
|
<van-cell title="押金赔付扣除" :value="`¥${money(order.checkout.deposit_deduct_amount)}`" value-class="red-text" />
|
||||||
<van-cell v-if="isRenter" title="退还租客押金" :value="`¥${money(order.checkout.renter_refund_amount)}`" value-class="green-text" />
|
<van-cell v-if="isRenter" title="退还租客" :value="`¥${money(order.checkout.renter_refund_amount)}`" value-class="green-text" />
|
||||||
<van-cell v-if="isOwner" title="号主最终收益" :value="`¥${money(order.checkout.owner_income_amount)}`" value-class="green-text" />
|
<van-cell v-if="isOwner" title="号主最终收入" :value="`¥${money(order.checkout.owner_income_amount)}`" value-class="green-text" />
|
||||||
<van-cell v-if="order.checkout.content" title="结账备注" :label="order.checkout.content" />
|
<van-cell v-if="order.checkout.content" title="结账备注" :label="order.checkout.content" />
|
||||||
<van-cell v-if="order.checkout.owner_adjustment_reason" title="号主修正原因" :label="order.checkout.owner_adjustment_reason" />
|
<van-cell v-if="order.checkout.owner_adjustment_reason" title="号主修正原因" :label="order.checkout.owner_adjustment_reason" />
|
||||||
</van-cell-group>
|
</van-cell-group>
|
||||||
@@ -911,9 +911,9 @@ function getStatusTagType(status: string) {
|
|||||||
<button class="popup-close" @click="showCounterPopup = false">✕</button>
|
<button class="popup-close" @click="showCounterPopup = false">✕</button>
|
||||||
</header>
|
</header>
|
||||||
<div class="popup-body">
|
<div class="popup-body">
|
||||||
<p class="form-hint-info">在此调整扣减租客押金或各项损耗的金额,提案将交由租客二次确认。</p>
|
<p class="form-hint-info">在此调整实际使用的租金项目和押金赔付金额,提案将交由租客二次确认。</p>
|
||||||
<van-cell-group :border="false">
|
<van-cell-group :border="false">
|
||||||
<van-field label="消耗损耗款">
|
<van-field label="额外消耗品已用">
|
||||||
<template #input>
|
<template #input>
|
||||||
<input v-model.number="counterForm.consumable_amount" type="number" class="custom-inline-input" />
|
<input v-model.number="counterForm.consumable_amount" type="number" class="custom-inline-input" />
|
||||||
</template>
|
</template>
|
||||||
@@ -923,12 +923,12 @@ function getStatusTagType(status: string) {
|
|||||||
<input v-model.number="counterForm.coin_consumed_m" type="number" class="custom-inline-input" />
|
<input v-model.number="counterForm.coin_consumed_m" type="number" class="custom-inline-input" />
|
||||||
</template>
|
</template>
|
||||||
</van-field>
|
</van-field>
|
||||||
<van-field label="其他损失扣款">
|
<van-field label="其他押金扣款">
|
||||||
<template #input>
|
<template #input>
|
||||||
<input v-model.number="counterForm.other_amount" type="number" class="custom-inline-input" />
|
<input v-model.number="counterForm.other_amount" type="number" class="custom-inline-input" />
|
||||||
</template>
|
</template>
|
||||||
</van-field>
|
</van-field>
|
||||||
<van-field label="扣除押金总额" required>
|
<van-field label="押金赔付扣除" required>
|
||||||
<template #input>
|
<template #input>
|
||||||
<input v-model.number="counterForm.deposit_deduct_amount" type="number" class="custom-inline-input" />
|
<input v-model.number="counterForm.deposit_deduct_amount" type="number" class="custom-inline-input" />
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user