修复预计 截止时间
This commit is contained in:
@@ -23,6 +23,7 @@ type OrderDTO struct {
|
||||
LoginPlatform string `json:"login_platform"`
|
||||
RentedAt *time.Time `json:"rented_at"`
|
||||
EstimatedDurationHours int `json:"estimated_duration_hours"`
|
||||
EstimatedEndAt *time.Time `json:"estimated_end_at,omitempty"`
|
||||
PriceRole string `json:"price_role,omitempty"`
|
||||
DisplayAmountCent int64 `json:"display_amount_cent"`
|
||||
RentAmountCent *int64 `json:"rent_amount_cent,omitempty"`
|
||||
|
||||
@@ -40,7 +40,7 @@ func (r *Repository) Create(ctx context.Context, renterID uint64, req CreateRequ
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rentHours := internalOrderHours
|
||||
rentHours := estimateOrderDurationHours(snapshot)
|
||||
pricing := buildOrderPricing(listing, account)
|
||||
depositOriginalAmountCent := listing.DepositAmountCent
|
||||
paidDepositAmountCent, waivedDepositAmountCent, err := r.depositAmountsForOrder(tx, renterID, depositOriginalAmountCent)
|
||||
@@ -339,8 +339,7 @@ func (r *Repository) ConfirmReceive(ctx context.Context, userID uint64, orderID
|
||||
}
|
||||
order.Status = orderStatusRenting
|
||||
order.HandoffStatus = handoffStatusReceived
|
||||
durationHours := orderDurationHours(order)
|
||||
order.EstimatedDurationHours = durationHours
|
||||
order.EstimatedDurationHours = estimateOrderDurationHours(order.AccountSnapshot)
|
||||
order.RentedAt = &now
|
||||
orderID := order.ID
|
||||
if err := notification.Append(tx, notification.Entry{
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
func (row orderRow) toAdminDTO() OrderDTO {
|
||||
rentedAt := row.RentedAt
|
||||
durationHours := orderDurationHours(row.RentalOrder)
|
||||
estimatedEndAt := orderEstimatedEndAt(row.RentalOrder)
|
||||
rentAmountCent := row.RentAmountCent
|
||||
ownerRentAmountCent := row.OwnerRentAmountCent
|
||||
platformFeeCent := row.PlatformFeeCent
|
||||
@@ -29,6 +30,7 @@ func (row orderRow) toAdminDTO() OrderDTO {
|
||||
LoginPlatform: row.LoginPlatform,
|
||||
RentedAt: rentedAt,
|
||||
EstimatedDurationHours: durationHours,
|
||||
EstimatedEndAt: estimatedEndAt,
|
||||
PriceRole: "admin",
|
||||
DisplayAmountCent: row.RentAmountCent,
|
||||
RentAmountCent: &rentAmountCent,
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"math"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"hfb_sys/backend/internal/model"
|
||||
"hfb_sys/backend/pkg/money"
|
||||
@@ -35,6 +36,38 @@ func orderDurationHours(order model.RentalOrder) int {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
hours := int(math.Round(days * 24))
|
||||
if hours <= 0 {
|
||||
return internalOrderHours
|
||||
}
|
||||
return hours
|
||||
}
|
||||
|
||||
func buildOrderPricing(listing model.RentalListing, account model.GameAccount) orderPricing {
|
||||
rentAmountCent := listing.PriceCent
|
||||
ownerRentAmountCent := int64(math.Round(readSnapshotPrice(account.AssetSummary, "seller_total_price") * 100))
|
||||
@@ -203,6 +236,24 @@ func readOrderSnapshotCoinM(raw datatypes.JSON) float64 {
|
||||
return roundQuantity(readJSONNumber(snapshot["haf_coin_amount"]) / 1000000)
|
||||
}
|
||||
|
||||
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 使用统一的角精度(0.1元)
|
||||
func roundMoney(value float64) float64 {
|
||||
return money.Round(value)
|
||||
|
||||
@@ -2,9 +2,11 @@ package order
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"hfb_sys/backend/internal/model"
|
||||
|
||||
"gorm.io/datatypes"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
@@ -378,6 +380,107 @@ func TestOrderDurationHoursUsesDefault(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEstimateOrderDurationHoursFromSnapshot(t *testing.T) {
|
||||
snapshot := datatypes.JSON([]byte(`{
|
||||
"haf_coin_amount": 165000000,
|
||||
"asset_summary": {
|
||||
"daily_loss_m": 10
|
||||
}
|
||||
}`))
|
||||
|
||||
hours := estimateOrderDurationHours(snapshot)
|
||||
if hours != 396 {
|
||||
t.Fatalf("hours = %d, want 396", hours)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEstimateOrderDurationHoursUsesDefaultWithoutDailyLoss(t *testing.T) {
|
||||
snapshot := datatypes.JSON([]byte(`{
|
||||
"haf_coin_amount": 165000000,
|
||||
"asset_summary": {}
|
||||
}`))
|
||||
|
||||
hours := estimateOrderDurationHours(snapshot)
|
||||
if hours != internalOrderHours {
|
||||
t.Fatalf("hours = %d, want %d", hours, internalOrderHours)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfirmReceiveRecalculatesEstimatedDuration(t *testing.T) {
|
||||
db := setupOrderTestDB(t)
|
||||
repo := NewRepository(db)
|
||||
|
||||
owner := model.User{Phone: "13800002001"}
|
||||
renter := model.User{Phone: "13900002001"}
|
||||
if err := db.Create(&owner).Error; err != nil {
|
||||
t.Fatalf("create owner failed: %v", err)
|
||||
}
|
||||
if err := db.Create(&renter).Error; err != nil {
|
||||
t.Fatalf("create renter failed: %v", err)
|
||||
}
|
||||
|
||||
account := model.GameAccount{
|
||||
OwnerID: owner.ID,
|
||||
Status: "rented",
|
||||
ServerRegion: "国服",
|
||||
LoginPlatform: "steam",
|
||||
Title: "测试账号",
|
||||
HafCoinAmount: 165000000,
|
||||
AssetSummary: datatypes.JSON([]byte(`{"daily_loss_m":10}`)),
|
||||
}
|
||||
if err := db.Create(&account).Error; err != nil {
|
||||
t.Fatalf("create account failed: %v", err)
|
||||
}
|
||||
|
||||
listing := model.RentalListing{
|
||||
ListingNo: "LST-DUR-001",
|
||||
OwnerID: owner.ID,
|
||||
AccountID: account.ID,
|
||||
Status: "published",
|
||||
ReviewStatus: "approved",
|
||||
InTransaction: true,
|
||||
PriceCent: 1000,
|
||||
}
|
||||
if err := db.Create(&listing).Error; err != nil {
|
||||
t.Fatalf("create listing failed: %v", err)
|
||||
}
|
||||
|
||||
snapshot, err := makeAccountSnapshot(account, listing)
|
||||
if err != nil {
|
||||
t.Fatalf("make snapshot failed: %v", err)
|
||||
}
|
||||
|
||||
order := model.RentalOrder{
|
||||
OrderNo: "ORD-DUR-001",
|
||||
ListingID: listing.ID,
|
||||
AccountID: account.ID,
|
||||
OwnerID: owner.ID,
|
||||
RenterID: renter.ID,
|
||||
EstimatedDurationHours: internalOrderHours,
|
||||
AccountSnapshot: snapshot,
|
||||
Status: orderStatusPendingHandoff,
|
||||
HandoffStatus: handoffStatusPendingRenterConfirm,
|
||||
}
|
||||
if err := db.Create(&order).Error; err != nil {
|
||||
t.Fatalf("create order failed: %v", err)
|
||||
}
|
||||
|
||||
if err := repo.ConfirmReceive(t.Context(), renter.ID, order.ID); err != nil {
|
||||
t.Fatalf("ConfirmReceive() error = %v", err)
|
||||
}
|
||||
|
||||
var saved model.RentalOrder
|
||||
if err := db.First(&saved, order.ID).Error; err != nil {
|
||||
t.Fatalf("load order failed: %v", err)
|
||||
}
|
||||
if saved.EstimatedDurationHours != 396 {
|
||||
t.Fatalf("EstimatedDurationHours = %d, want 396", saved.EstimatedDurationHours)
|
||||
}
|
||||
if saved.RentedAt == nil || saved.RentedAt.After(time.Now()) {
|
||||
t.Fatalf("RentedAt not set correctly: %#v", saved.RentedAt)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCancelClosesPendingPaymentOrder 验证租客取消待支付订单时,未完成支付流水会同步关闭。
|
||||
func TestCancelClosesPendingPaymentOrder(t *testing.T) {
|
||||
db := setupOrderTestDB(t)
|
||||
|
||||
@@ -109,6 +109,7 @@ function orderRentedAt() {
|
||||
|
||||
function orderEstimatedEndAt() {
|
||||
if (!order.value) return undefined
|
||||
if (order.value.estimated_end_at) return order.value.estimated_end_at
|
||||
const rentedAt = orderRentedAt()
|
||||
const durationHours = Number(order.value.estimated_duration_hours || 0)
|
||||
if (!rentedAt || durationHours <= 0) return undefined
|
||||
|
||||
@@ -18,6 +18,7 @@ export interface Order {
|
||||
login_platform: string
|
||||
rented_at?: string
|
||||
estimated_duration_hours: number
|
||||
estimated_end_at?: string
|
||||
price_role?: 'renter' | 'owner' | 'admin' | string
|
||||
display_amount_cent: number
|
||||
rent_amount_cent?: number
|
||||
|
||||
@@ -211,6 +211,7 @@ export function getSnapshotHafCoinM(order: Order | null) {
|
||||
}
|
||||
|
||||
export function orderEstimatedEndAt(order: Order | null) {
|
||||
if (order?.estimated_end_at) return order.estimated_end_at
|
||||
const rentedAt = order?.rented_at
|
||||
const durationHours = Number(order?.estimated_duration_hours || 0)
|
||||
if (!rentedAt || durationHours <= 0) return undefined
|
||||
|
||||
Reference in New Issue
Block a user