package order import ( "testing" "time" "hfb_sys/backend/internal/model" "hfb_sys/backend/internal/timeutil" "gorm.io/datatypes" ) func TestCalculateCheckoutSettlementRefundsUnusedRent(t *testing.T) { order := model.RentalOrder{ RentAmountCent: 38300, OwnerRentAmountCent: 35300, DepositAmountCent: 15000, 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) // 角精度:243.7 = roundMoney(236.7 + 7) if float64(settlement.ActualRentAmountCent)/100 != 243.7 { t.Fatalf("ActualRentAmount = %.1f, want 243.7", float64(settlement.ActualRentAmountCent)/100) } // 角精度:216.7 = roundMoney(209.7 + 7) 卖家币价+消耗品 if float64(settlement.OwnerRentIncomeCent)/100 != 216.7 { t.Fatalf("OwnerRentIncome = %.1f, want 216.7", float64(settlement.OwnerRentIncomeCent)/100) } // 角精度:27.0 = roundMoney(27) 平台费 if float64(settlement.PlatformFeeCent)/100 != 27.0 { t.Fatalf("PlatformFee = %.1f, want 27.0", float64(settlement.PlatformFeeCent)/100) } // 角精度:139.3 = roundMoney(383 - 243.7) if float64(settlement.RentRefundCent)/100 != 139.3 { t.Fatalf("RentRefund = %.1f, want 139.3", float64(settlement.RentRefundCent)/100) } if float64(settlement.DepositRefundCent)/100 != 150 { t.Fatalf("DepositRefund = %.1f, want 150.0", float64(settlement.DepositRefundCent)/100) } // 角精度:289.3 = roundMoney(139.3 + 150) if float64(settlement.RenterRefundCent)/100 != 289.3 { t.Fatalf("RenterRefund = %.1f, want 289.3", float64(settlement.RenterRefundCent)/100) } } func TestCalculateCheckoutSettlementUsesBuyerAndSellerRatiosSeparately(t *testing.T) { order := model.RentalOrder{ RentAmountCent: 38300, OwnerRentAmountCent: 35300, DepositAmountCent: 15000, 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) // 角精度:131.5 = roundMoney(131.5) if float64(settlement.ActualRentAmountCent)/100 != 131.5 { t.Fatalf("租客侧实际租金 = %.1f, want 131.5", float64(settlement.ActualRentAmountCent)/100) } // 角精度:116.5 = roundMoney(116.5) if float64(settlement.OwnerRentIncomeCent)/100 != 116.5 { t.Fatalf("卖家侧租金收入 = %.1f, want 116.5", float64(settlement.OwnerRentIncomeCent)/100) } // 角精度:15.0 = roundMoney(15) if float64(settlement.PlatformFeeCent)/100 != 15.0 { t.Fatalf("平台差价 = %.1f, want 15.0", float64(settlement.PlatformFeeCent)/100) } } func TestCalculateCheckoutSettlementAddsDepositCompensation(t *testing.T) { order := model.RentalOrder{ RentAmountCent: 38300, OwnerRentAmountCent: 35300, DepositAmountCent: 15000, 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 float64(settlement.ActualRentAmountCent)/100 != 383 { t.Fatalf("ActualRentAmount = %.2f, want 383.00", float64(settlement.ActualRentAmountCent)/100) } if float64(settlement.OwnerRentIncomeCent)/100 != 353 { t.Fatalf("OwnerRentIncome = %.2f, want 353.00", float64(settlement.OwnerRentIncomeCent)/100) } if float64(settlement.DepositCompensationCent)/100 != 30 { t.Fatalf("DepositCompensation = %.2f, want 30.00", float64(settlement.DepositCompensationCent)/100) } if float64(settlement.OwnerIncomeCent)/100 != 383 { t.Fatalf("OwnerIncome = %.2f, want 383.00", float64(settlement.OwnerIncomeCent)/100) } if float64(settlement.RenterRefundCent)/100 != 120 { t.Fatalf("RenterRefund = %.2f, want 120.00", float64(settlement.RenterRefundCent)/100) } } func TestCalculateDepositWaiverUsesSharedRemainingQuota(t *testing.T) { paid, waived := calculateDepositWaiver(500, 300, 0) if paid != 200 || waived != 300 { t.Fatalf("paid = %.1f, waived = %.1f, want 200.0, 300.0", paid, waived) } paid, waived = calculateDepositWaiver(500, 300, 200) if paid != 400 || waived != 100 { t.Fatalf("paid = %.1f, waived = %.1f, want 400.0, 100.0", paid, waived) } paid, waived = calculateDepositWaiver(500, 300, 300) if paid != 500 || waived != 0 { t.Fatalf("paid = %.1f, waived = %.1f, want 500.0, 0.0", paid, waived) } } func TestArchiveListingAfterCheckoutMovesListingOffline(t *testing.T) { // This test is purely for contract documentation; no behavior is tested yet. // When implementing auto-archive behavior: // - Call should succeed for renting/overdue/pending_checkout_confirm/pending_checkout_accept orders // - Listing status should be set to "archived" or "offline" // - In-transaction flag should be cleared // - Order should enter completed or closed state _ = time.Now() } func TestNewOrderNoUsesShanghaiTimeWhenLocalIsUTC(t *testing.T) { oldLocal := time.Local time.Local = time.UTC defer func() { time.Local = oldLocal }() loc := timeutil.ShanghaiLocation() before := time.Now().In(loc) orderNo, err := newOrderNo() after := time.Now().In(loc) if err != nil { t.Fatalf("newOrderNo() error = %v", err) } if len(orderNo) != len("RO20260603123456789") { t.Fatalf("orderNo length = %d, want %d: %s", len(orderNo), len("RO20260603123456789"), orderNo) } parsed, err := time.ParseInLocation("20060102150405", orderNo[2:16], loc) if err != nil { t.Fatalf("parse order no time: %v", err) } if parsed.Before(before.Add(-2*time.Second)) || parsed.After(after.Add(2*time.Second)) { t.Fatalf("order no time = %s, want between %s and %s", parsed, before, after) } }