实现租客成长等级并优化展示

This commit is contained in:
yml2213
2026-07-26 11:25:25 +08:00
parent 756fa342cb
commit 321f0781cf
27 changed files with 948 additions and 16 deletions
@@ -5,6 +5,7 @@ import (
"hfb_sys/backend/internal/model"
"hfb_sys/backend/internal/modules/notification"
"hfb_sys/backend/internal/modules/rentergrowth"
"hfb_sys/backend/internal/modules/wallet"
"gorm.io/gorm"
@@ -42,6 +43,9 @@ func (r *Repository) finalizeCheckout(tx *gorm.DB, order *model.RentalOrder, che
if err := appendCheckoutCompletedNotifications(tx, order, renterContent); err != nil {
return nil, err
}
if err := rentergrowth.AwardOrderCompleted(tx, order, settlement.ActualRentAmountCent, rentergrowth.SourceOrderCompleted); err != nil {
return nil, err
}
if err := saveFinalizedCheckout(tx, order, checkout, listing, account); err != nil {
return nil, err
}
+7
View File
@@ -32,6 +32,13 @@ type OrderDTO struct {
DepositOriginalAmountCent int64 `json:"deposit_original_amount_cent"`
DepositWaivedAmountCent int64 `json:"deposit_waived_amount_cent"`
PlatformFeeCent *int64 `json:"platform_fee_cent,omitempty"`
RentOriginalAmountCent int64 `json:"rent_original_amount_cent,omitempty"`
RentDiscountAmountCent int64 `json:"rent_discount_amount_cent,omitempty"`
RenterGrowthLevel string `json:"renter_growth_level,omitempty"`
RenterGrowthLevelName string `json:"renter_growth_level_name,omitempty"`
RenterDiscountBps int `json:"renter_discount_bps,omitempty"`
GrowthPointsAwarded int64 `json:"growth_points_awarded,omitempty"`
GrowthPointsAwardedAt *time.Time `json:"growth_points_awarded_at,omitempty"`
AccountSnapshot datatypes.JSON `json:"account_snapshot"`
Status string `json:"status"`
HandoffStatus string `json:"handoff_status"`
@@ -8,6 +8,7 @@ import (
"hfb_sys/backend/internal/model"
"hfb_sys/backend/internal/modules/chat"
"hfb_sys/backend/internal/modules/notification"
"hfb_sys/backend/internal/modules/rentergrowth"
"gorm.io/datatypes"
"gorm.io/gorm"
@@ -42,6 +43,16 @@ func (r *Repository) Create(ctx context.Context, renterID uint64, req CreateRequ
}
rentHours := estimateOrderDurationHours(snapshot)
pricing := buildOrderPricing(listing, account)
rentOriginalAmountCent := pricing.RentAmountCent
growthSnapshot, err := rentergrowth.SnapshotForUser(tx, renterID)
if err != nil {
return err
}
rentDiscountAmountCent := rentergrowth.CalculateDiscountCent(rentOriginalAmountCent, pricing.PlatformFeeCent, growthSnapshot.DiscountBps)
if rentDiscountAmountCent > 0 {
pricing.PlatformFeeCent -= rentDiscountAmountCent
pricing.RentAmountCent -= rentDiscountAmountCent
}
depositOriginalAmountCent := listing.DepositAmountCent
paidDepositAmountCent, waivedDepositAmountCent, err := r.depositAmountsForOrder(tx, renterID, depositOriginalAmountCent)
if err != nil {
@@ -60,6 +71,11 @@ func (r *Repository) Create(ctx context.Context, renterID uint64, req CreateRequ
DepositOriginalAmountCent: depositOriginalAmountCent,
DepositWaivedAmountCent: waivedDepositAmountCent,
PlatformFeeCent: pricing.PlatformFeeCent,
RentOriginalAmountCent: rentOriginalAmountCent,
RentDiscountAmountCent: rentDiscountAmountCent,
RenterGrowthLevel: growthSnapshot.LevelCode,
RenterGrowthLevelName: growthSnapshot.LevelName,
RenterDiscountBps: growthSnapshot.DiscountBps,
AccountSnapshot: snapshot,
Status: orderStatusPendingPayment,
HandoffStatus: handoffStatusNone,
@@ -82,6 +82,13 @@ func (row orderRow) toAdminDTO() OrderDTO {
DepositOriginalAmountCent: effectiveDepositOriginalAmountCent(row.RentalOrder),
DepositWaivedAmountCent: row.DepositWaivedAmountCent,
PlatformFeeCent: &platformFeeCent,
RentOriginalAmountCent: effectiveRentOriginalAmountCent(row.RentalOrder),
RentDiscountAmountCent: row.RentDiscountAmountCent,
RenterGrowthLevel: effectiveRenterGrowthLevel(row.RentalOrder),
RenterGrowthLevelName: effectiveRenterGrowthLevelName(row.RentalOrder),
RenterDiscountBps: effectiveRenterDiscountBps(row.RentalOrder),
GrowthPointsAwarded: row.GrowthPointsAwarded,
GrowthPointsAwardedAt: row.GrowthPointsAwardedAt,
AccountSnapshot: row.AccountSnapshot,
Status: row.Status,
HandoffStatus: row.HandoffStatus,
@@ -154,6 +161,34 @@ func effectiveDepositOriginalAmountCent(order model.RentalOrder) int64 {
return order.DepositAmountCent
}
func effectiveRentOriginalAmountCent(order model.RentalOrder) int64 {
if order.RentOriginalAmountCent > 0 {
return order.RentOriginalAmountCent
}
return order.RentAmountCent + order.RentDiscountAmountCent
}
func effectiveRenterGrowthLevel(order model.RentalOrder) string {
if order.RenterGrowthLevel != "" {
return order.RenterGrowthLevel
}
return "normal"
}
func effectiveRenterGrowthLevelName(order model.RentalOrder) string {
if order.RenterGrowthLevelName != "" {
return order.RenterGrowthLevelName
}
return "普通"
}
func effectiveRenterDiscountBps(order model.RentalOrder) int {
if order.RenterDiscountBps > 0 {
return order.RenterDiscountBps
}
return 10000
}
func toCheckoutAdminDTO(checkout model.OrderCheckout) CheckoutDTO {
rentAmountCent := checkout.RentAmountCent
ownerRentAmountCent := checkout.OwnerRentAmountCent
@@ -275,6 +310,13 @@ func applyOrderPriceView(dto *OrderDTO, order model.RentalOrder, userID uint64)
dto.DisplayAmountCent = ownerAmountCent
dto.RentAmountCent = nil
dto.OwnerRentAmountCent = &ownerAmountCent
dto.RentOriginalAmountCent = 0
dto.RentDiscountAmountCent = 0
dto.RenterGrowthLevel = ""
dto.RenterGrowthLevelName = ""
dto.RenterDiscountBps = 0
dto.GrowthPointsAwarded = 0
dto.GrowthPointsAwardedAt = nil
sanitizeOrderSnapshot(&dto.AccountSnapshot, "owner")
case userID == order.RenterID:
rentAmountCent := order.RentAmountCent
@@ -282,12 +324,24 @@ func applyOrderPriceView(dto *OrderDTO, order model.RentalOrder, userID uint64)
dto.DisplayAmountCent = rentAmountCent
dto.RentAmountCent = &rentAmountCent
dto.OwnerRentAmountCent = nil
dto.RentOriginalAmountCent = effectiveRentOriginalAmountCent(order)
dto.RentDiscountAmountCent = order.RentDiscountAmountCent
dto.RenterGrowthLevel = effectiveRenterGrowthLevel(order)
dto.RenterGrowthLevelName = effectiveRenterGrowthLevelName(order)
dto.RenterDiscountBps = effectiveRenterDiscountBps(order)
sanitizeOrderSnapshot(&dto.AccountSnapshot, "renter")
default:
dto.PriceRole = ""
dto.DisplayAmountCent = 0
dto.RentAmountCent = nil
dto.OwnerRentAmountCent = nil
dto.RentOriginalAmountCent = 0
dto.RentDiscountAmountCent = 0
dto.RenterGrowthLevel = ""
dto.RenterGrowthLevelName = ""
dto.RenterDiscountBps = 0
dto.GrowthPointsAwarded = 0
dto.GrowthPointsAwardedAt = nil
sanitizeOrderSnapshot(&dto.AccountSnapshot, "")
}
}
@@ -140,6 +140,65 @@ func TestRepositoryCreateOrderRejectsOwnListing(t *testing.T) {
}
}
func TestRepositoryCreateAppliesRenterGrowthDiscount(t *testing.T) {
db := setupOrderTestDB(t)
repo := NewRepository(db)
owner := model.User{Phone: "13800001101"}
renter := model.User{
Phone: "13800001102",
RenterGrowthPoints: 300,
RenterGrowthLevel: "platinum",
}
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,
ServerRegion: "国服",
LoginPlatform: "steam",
Title: "测试账号",
AssetSummary: datatypes.JSON([]byte(`{"price_breakdown":{"seller_total_price":90,"platform_markup_amount":10}}`)),
}
if err := db.Create(&account).Error; err != nil {
t.Fatalf("create account failed: %v", err)
}
listing := model.RentalListing{
AccountID: account.ID,
OwnerID: owner.ID,
PriceCent: 10000,
Status: listingStatusPublished,
ReviewStatus: listingReviewStatusApproved,
}
if err := db.Create(&listing).Error; err != nil {
t.Fatalf("create listing failed: %v", err)
}
dto, err := repo.Create(t.Context(), renter.ID, CreateRequest{ListingID: listing.ID})
if err != nil {
t.Fatalf("Create() error = %v", err)
}
if dto.RentAmountCent == nil || *dto.RentAmountCent != 9900 {
t.Fatalf("rent amount = %v, want 9900", dto.RentAmountCent)
}
if dto.RentOriginalAmountCent != 10000 || dto.RentDiscountAmountCent != 100 {
t.Fatalf("discount snapshot = original %d discount %d, want 10000/100", dto.RentOriginalAmountCent, dto.RentDiscountAmountCent)
}
if dto.RenterGrowthLevel != "platinum" || dto.RenterGrowthLevelName != "铂金" || dto.RenterDiscountBps != 9900 {
t.Fatalf("growth snapshot = %s/%s/%d, want platinum/铂金/9900", dto.RenterGrowthLevel, dto.RenterGrowthLevelName, dto.RenterDiscountBps)
}
var saved model.RentalOrder
if err := db.First(&saved, dto.ID).Error; err != nil {
t.Fatalf("find saved order failed: %v", err)
}
if saved.OwnerRentAmountCent != 9000 || saved.PlatformFeeCent != 900 {
t.Fatalf("pricing = owner %d platform %d, want 9000/900", saved.OwnerRentAmountCent, saved.PlatformFeeCent)
}
}
// TestBuildOrderPricing 测试订单定价计算
func TestBuildOrderPricingBasic(t *testing.T) {
listing := model.RentalListing{