彻底优化数据库字段
This commit is contained in:
@@ -298,10 +298,7 @@ func (j *Job) handleReturnOverdue(ctx context.Context, now time.Time, cfg thresh
|
||||
var rows []model.RentalOrder
|
||||
overdueBefore := now.Add(-time.Duration(cfg.ReturnOverdueGraceMinutes) * time.Minute)
|
||||
err := j.db.WithContext(ctx).
|
||||
Where(`status = ? AND (
|
||||
(rented_at IS NOT NULL AND TIMESTAMPADD(HOUR, COALESCE(NULLIF(estimated_duration_hours, 0), 24), rented_at) <= ?)
|
||||
OR (rented_at IS NULL AND rent_end_at IS NOT NULL AND rent_end_at <= ?)
|
||||
)`, "renting", overdueBefore, overdueBefore).
|
||||
Where(`status = ? AND rented_at IS NOT NULL AND TIMESTAMPADD(HOUR, COALESCE(NULLIF(estimated_duration_hours, 0), 24), rented_at) <= ?`, "renting", overdueBefore).
|
||||
Order("id ASC").
|
||||
Limit(100).
|
||||
Find(&rows).Error
|
||||
|
||||
@@ -14,7 +14,7 @@ type Dispute struct {
|
||||
Type string `gorm:"size:32;not null" json:"type"`
|
||||
Status string `gorm:"size:32;not null;default:'open'" json:"status"`
|
||||
Description string `json:"description"`
|
||||
EvidenceURLS datatypes.JSON `json:"evidence_urls"`
|
||||
EvidenceURLS datatypes.JSON `gorm:"column:evidence_urls" json:"evidence_urls"`
|
||||
ArbitrationResult string `gorm:"size:32;not null;default:''" json:"arbitration_result"`
|
||||
ArbitrationRemark string `json:"arbitration_remark"`
|
||||
HandledBy *uint64 `json:"handled_by"`
|
||||
|
||||
@@ -33,9 +33,6 @@ type RentalListing struct {
|
||||
AccountID uint64 `gorm:"not null;index" json:"account_id"`
|
||||
OwnerID uint64 `gorm:"not null;index" json:"owner_id"`
|
||||
Price float64 `gorm:"type:decimal(12,2);not null;default:0" json:"price"`
|
||||
PriceHourly float64 `gorm:"type:decimal(12,2);not null;default:0" json:"price_hourly"`
|
||||
PriceDaily float64 `gorm:"type:decimal(12,2);not null;default:0" json:"price_daily"`
|
||||
PriceWeekly float64 `gorm:"type:decimal(12,2);not null;default:0" json:"price_weekly"`
|
||||
DepositAmount float64 `gorm:"type:decimal(12,2);not null;default:0" json:"deposit_amount"`
|
||||
InTransaction bool `gorm:"not null;default:false" json:"in_transaction"`
|
||||
Status string `gorm:"size:32;not null;default:'draft'" json:"status"`
|
||||
|
||||
@@ -15,14 +15,12 @@ type RentalOrder struct {
|
||||
RenterID uint64 `gorm:"not null;index" json:"renter_id"`
|
||||
RentedAt *time.Time `json:"rented_at"`
|
||||
EstimatedDurationHours int `gorm:"not null;default:24" json:"estimated_duration_hours"`
|
||||
RentStartAt *time.Time `json:"rent_start_at"`
|
||||
RentEndAt *time.Time `json:"rent_end_at"`
|
||||
RentHours int `gorm:"not null" json:"rent_hours"`
|
||||
RentAmount float64 `gorm:"type:decimal(12,2);not null;default:0" json:"rent_amount"`
|
||||
OwnerRentAmount float64 `gorm:"type:decimal(12,2);not null;default:0" json:"owner_rent_amount"`
|
||||
DepositAmount float64 `gorm:"type:decimal(12,2);not null;default:0" json:"deposit_amount"`
|
||||
PlatformFee float64 `gorm:"type:decimal(12,2);not null;default:0" json:"platform_fee"`
|
||||
AccountSnapshot datatypes.JSON `json:"account_snapshot"`
|
||||
Status string `gorm:"size:32;not null;default:'pending_confirm'" json:"status"`
|
||||
Status string `gorm:"size:32;not null;default:'pending_payment'" json:"status"`
|
||||
HandoffStatus string `gorm:"size:32;not null;default:'none'" json:"handoff_status"`
|
||||
SettlementStatus string `gorm:"size:32;not null;default:'unsettled'" json:"settlement_status"`
|
||||
OwnerSettledAt *time.Time `json:"owner_settled_at"`
|
||||
|
||||
@@ -12,6 +12,8 @@ type OrderCheckout struct {
|
||||
InitiatedBy uint64 `gorm:"not null" json:"initiated_by"`
|
||||
Status string `gorm:"size:32;not null;default:'submitted'" json:"status"`
|
||||
RentAmount float64 `gorm:"type:decimal(12,2);not null;default:0" json:"rent_amount"`
|
||||
OwnerRentAmount float64 `gorm:"type:decimal(12,2);not null;default:0" json:"owner_rent_amount"`
|
||||
PlatformFee float64 `gorm:"type:decimal(12,2);not null;default:0" json:"platform_fee"`
|
||||
DepositAmount float64 `gorm:"type:decimal(12,2);not null;default:0" json:"deposit_amount"`
|
||||
ConsumableAmount float64 `gorm:"type:decimal(12,2);not null;default:0" json:"consumable_amount"`
|
||||
CoinConsumedM float64 `gorm:"type:decimal(12,2);not null;default:0" json:"coin_consumed_m"`
|
||||
@@ -20,7 +22,7 @@ type OrderCheckout struct {
|
||||
RenterRefundAmount float64 `gorm:"type:decimal(12,2);not null;default:0" json:"renter_refund_amount"`
|
||||
OwnerIncomeAmount float64 `gorm:"type:decimal(12,2);not null;default:0" json:"owner_income_amount"`
|
||||
Content string `json:"content"`
|
||||
EvidenceURLS datatypes.JSON `json:"evidence_urls"`
|
||||
EvidenceURLS datatypes.JSON `gorm:"column:evidence_urls" json:"evidence_urls"`
|
||||
OwnerAdjustmentReason string `json:"owner_adjustment_reason"`
|
||||
OwnerAdjustedAt *time.Time `json:"owner_adjusted_at"`
|
||||
RenterConfirmedAt *time.Time `json:"renter_confirmed_at"`
|
||||
|
||||
@@ -303,6 +303,10 @@ type arbitrationSettlement struct {
|
||||
|
||||
func buildArbitrationSettlement(order model.RentalOrder, req ArbitrateRequest) (arbitrationSettlement, error) {
|
||||
total := order.RentAmount + order.DepositAmount
|
||||
ownerRentAmount := order.OwnerRentAmount
|
||||
if ownerRentAmount <= 0 || ownerRentAmount > order.RentAmount {
|
||||
ownerRentAmount = order.RentAmount
|
||||
}
|
||||
settlement := arbitrationSettlement{}
|
||||
orderID := order.ID
|
||||
if total > 0 {
|
||||
@@ -359,9 +363,9 @@ func buildArbitrationSettlement(order model.RentalOrder, req ArbitrateRequest) (
|
||||
return settlement, ErrInvalidDispute
|
||||
}
|
||||
addRenterRefund(req.Amount, "仲裁部分退款")
|
||||
addOwnerIncome(total-req.Amount, "仲裁剩余金额结算给号主")
|
||||
addOwnerIncome(minMoney(total-req.Amount, ownerRentAmount+order.DepositAmount), "仲裁剩余金额结算给号主")
|
||||
case "release_deposit":
|
||||
addOwnerIncome(order.RentAmount, "仲裁确认订单金额结算给号主")
|
||||
addOwnerIncome(ownerRentAmount, "仲裁确认订单金额结算给号主")
|
||||
addRenterRefund(order.DepositAmount, "仲裁释放押金给租客")
|
||||
case "deduct_deposit", "compensate_owner":
|
||||
deductAmount := req.Amount
|
||||
@@ -372,7 +376,7 @@ func buildArbitrationSettlement(order model.RentalOrder, req ArbitrateRequest) (
|
||||
return settlement, ErrInvalidDispute
|
||||
}
|
||||
settlement.DepositDeductAmount = deductAmount
|
||||
addOwnerIncome(order.RentAmount+deductAmount, "仲裁订单金额及押金赔付结算给号主")
|
||||
addOwnerIncome(ownerRentAmount+deductAmount, "仲裁订单金额及押金赔付结算给号主")
|
||||
addRenterRefund(order.DepositAmount-deductAmount, "仲裁退回剩余押金给租客")
|
||||
case "order_close":
|
||||
// Only release frozen funds. No available-balance settlement happens in development mode.
|
||||
@@ -384,6 +388,13 @@ func buildArbitrationSettlement(order model.RentalOrder, req ArbitrateRequest) (
|
||||
return settlement, nil
|
||||
}
|
||||
|
||||
func minMoney(a float64, b float64) float64 {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (r *Repository) baseQuery() *gorm.DB {
|
||||
return r.db.Table("disputes AS d").
|
||||
Select("d.*, o.order_no, a.title").
|
||||
|
||||
@@ -19,9 +19,6 @@ type ListingDTO struct {
|
||||
ScreenshotURLS []string `json:"screenshot_urls"`
|
||||
CoverURL string `json:"cover_url"`
|
||||
Price float64 `json:"price"`
|
||||
PriceHourly float64 `json:"price_hourly"`
|
||||
PriceDaily float64 `json:"price_daily"`
|
||||
PriceWeekly float64 `json:"price_weekly"`
|
||||
DepositAmount float64 `json:"deposit_amount"`
|
||||
IsAccelerated bool `json:"is_accelerated_sale"`
|
||||
InTransaction bool `json:"in_transaction"`
|
||||
@@ -43,9 +40,6 @@ type CreateRequest struct {
|
||||
AssetSummary map[string]any `json:"asset_summary"`
|
||||
ScreenshotURLS []string `json:"screenshot_urls"`
|
||||
Price float64 `json:"price"`
|
||||
PriceHourly float64 `json:"price_hourly"`
|
||||
PriceDaily float64 `json:"price_daily"`
|
||||
PriceWeekly float64 `json:"price_weekly"`
|
||||
DepositAmount float64 `json:"deposit_amount"`
|
||||
}
|
||||
|
||||
|
||||
@@ -364,6 +364,8 @@ func writeListingError(c *gin.Context, err error) {
|
||||
response.BadRequest(c, "发布价格不正确")
|
||||
case errors.Is(err, ErrInvalidDeposit):
|
||||
response.BadRequest(c, "押金不能小于 0")
|
||||
case errors.Is(err, ErrDepositTooLow):
|
||||
response.BadRequest(c, "押金必须大于额外消耗品总价值")
|
||||
case errors.Is(err, ErrInvalidHafCoin):
|
||||
response.BadRequest(c, "哈夫币数量不正确")
|
||||
case errors.Is(err, ErrMissingScreenshot):
|
||||
|
||||
@@ -65,9 +65,6 @@ func (r *Repository) Create(ownerID uint64, req CreateRequest, reviewRequired bo
|
||||
AccountID: account.ID,
|
||||
OwnerID: ownerID,
|
||||
Price: price,
|
||||
PriceHourly: listingHourlyPrice(req, price),
|
||||
PriceDaily: listingDailyPrice(req, price),
|
||||
PriceWeekly: listingWeeklyPrice(req, price),
|
||||
DepositAmount: req.DepositAmount,
|
||||
Status: listingStatus,
|
||||
ReviewStatus: reviewStatus,
|
||||
@@ -112,9 +109,6 @@ func (r *Repository) Update(ownerID uint64, listingID uint64, req UpdateRequest,
|
||||
|
||||
price := normalizedListingPrice(req)
|
||||
listing.Price = price
|
||||
listing.PriceHourly = listingHourlyPrice(req, price)
|
||||
listing.PriceDaily = listingDailyPrice(req, price)
|
||||
listing.PriceWeekly = listingWeeklyPrice(req, price)
|
||||
listing.DepositAmount = req.DepositAmount
|
||||
listingStatus, reviewStatus, publishedAt := initialPublishState(reviewRequired)
|
||||
listing.Status = listingStatus
|
||||
@@ -489,63 +483,7 @@ func rowsToDTO(rows []listingRow) []ListingDTO {
|
||||
}
|
||||
|
||||
func normalizedListingPrice(req CreateRequest) float64 {
|
||||
if req.Price > 0 {
|
||||
return req.Price
|
||||
}
|
||||
if req.PriceDaily > 0 {
|
||||
return req.PriceDaily
|
||||
}
|
||||
if req.PriceHourly > 0 {
|
||||
return req.PriceHourly * 24
|
||||
}
|
||||
if req.PriceWeekly > 0 {
|
||||
return req.PriceWeekly / 7
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func listingHourlyPrice(req CreateRequest, price float64) float64 {
|
||||
if req.PriceHourly > 0 {
|
||||
return req.PriceHourly
|
||||
}
|
||||
return price / 24
|
||||
}
|
||||
|
||||
func listingDailyPrice(req CreateRequest, price float64) float64 {
|
||||
if req.PriceDaily > 0 {
|
||||
return req.PriceDaily
|
||||
}
|
||||
return price
|
||||
}
|
||||
|
||||
func listingWeeklyPrice(req CreateRequest, price float64) float64 {
|
||||
if req.PriceWeekly > 0 {
|
||||
return req.PriceWeekly
|
||||
}
|
||||
return price * 7
|
||||
}
|
||||
|
||||
func listingDisplayPrices(price float64, hourly float64, daily float64, weekly float64) (float64, float64, float64, float64) {
|
||||
if price <= 0 {
|
||||
switch {
|
||||
case daily > 0:
|
||||
price = daily
|
||||
case hourly > 0:
|
||||
price = hourly * 24
|
||||
case weekly > 0:
|
||||
price = weekly / 7
|
||||
}
|
||||
}
|
||||
if daily <= 0 {
|
||||
daily = price
|
||||
}
|
||||
if hourly <= 0 && price > 0 {
|
||||
hourly = price / 24
|
||||
}
|
||||
if weekly <= 0 && price > 0 {
|
||||
weekly = price * 7
|
||||
}
|
||||
return price, hourly, daily, weekly
|
||||
return req.Price
|
||||
}
|
||||
|
||||
func publicListings(items []ListingDTO) []ListingDTO {
|
||||
@@ -565,7 +503,6 @@ func applyPublicListingURLs(item *ListingDTO) {
|
||||
func (row listingRow) toDTO() ListingDTO {
|
||||
assetSummary := decodeAssetSummary(row.AssetSummary)
|
||||
screenshotURLS := cleanScreenshotURLs(decodeScreenshots(row.ScreenshotURLS))
|
||||
price, priceHourly, priceDaily, priceWeekly := listingDisplayPrices(row.Price, row.PriceHourly, row.PriceDaily, row.PriceWeekly)
|
||||
return ListingDTO{
|
||||
ID: row.ID,
|
||||
AccountID: row.AccountID,
|
||||
@@ -582,10 +519,7 @@ func (row listingRow) toDTO() ListingDTO {
|
||||
AssetSummary: assetSummary,
|
||||
ScreenshotURLS: screenshotURLS,
|
||||
CoverURL: publicCoverURL(row.ID, screenshotURLS, row.Status, row.ReviewStatus),
|
||||
Price: price,
|
||||
PriceHourly: priceHourly,
|
||||
PriceDaily: priceDaily,
|
||||
PriceWeekly: priceWeekly,
|
||||
Price: row.Price,
|
||||
DepositAmount: row.DepositAmount,
|
||||
IsAccelerated: isAcceleratedSale(assetSummary),
|
||||
InTransaction: row.InTransaction,
|
||||
@@ -601,7 +535,6 @@ func (row listingRow) toDTO() ListingDTO {
|
||||
func toDTO(account model.GameAccount, listing model.RentalListing) *ListingDTO {
|
||||
assetSummary := decodeAssetSummary(account.AssetSummary)
|
||||
screenshotURLS := cleanScreenshotURLs(decodeScreenshots(account.ScreenshotURLS))
|
||||
price, priceHourly, priceDaily, priceWeekly := listingDisplayPrices(listing.Price, listing.PriceHourly, listing.PriceDaily, listing.PriceWeekly)
|
||||
return &ListingDTO{
|
||||
ID: listing.ID,
|
||||
AccountID: account.ID,
|
||||
@@ -616,10 +549,7 @@ func toDTO(account model.GameAccount, listing model.RentalListing) *ListingDTO {
|
||||
AssetSummary: assetSummary,
|
||||
ScreenshotURLS: screenshotURLS,
|
||||
CoverURL: publicCoverURL(listing.ID, screenshotURLS, listing.Status, listing.ReviewStatus),
|
||||
Price: price,
|
||||
PriceHourly: priceHourly,
|
||||
PriceDaily: priceDaily,
|
||||
PriceWeekly: priceWeekly,
|
||||
Price: listing.Price,
|
||||
DepositAmount: listing.DepositAmount,
|
||||
IsAccelerated: isAcceleratedSale(assetSummary),
|
||||
InTransaction: listing.InTransaction,
|
||||
|
||||
@@ -3,6 +3,8 @@ package listing
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"math"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
@@ -15,6 +17,7 @@ var (
|
||||
ErrMissingServerRegion = errors.New("missing server region")
|
||||
ErrInvalidPrice = errors.New("invalid listing price")
|
||||
ErrInvalidDeposit = errors.New("invalid listing deposit")
|
||||
ErrDepositTooLow = errors.New("listing deposit too low")
|
||||
ErrInvalidHafCoin = errors.New("invalid haf coin amount")
|
||||
ErrMissingScreenshot = errors.New("missing screenshot")
|
||||
)
|
||||
@@ -34,6 +37,8 @@ const (
|
||||
defaultFireLevelMin = 38
|
||||
)
|
||||
|
||||
var priceNumberPattern = regexp.MustCompile(`(\d+(?:\.\d+)?)`)
|
||||
|
||||
type FireLevelTooLowError struct {
|
||||
Min int
|
||||
}
|
||||
@@ -217,6 +222,9 @@ func validateRequest(req CreateRequest, rules publishRules) error {
|
||||
if req.DepositAmount < 0 {
|
||||
return ErrInvalidDeposit
|
||||
}
|
||||
if consumables := consumableValue(req.AssetSummary); consumables > 0 && req.DepositAmount <= consumables {
|
||||
return ErrDepositTooLow
|
||||
}
|
||||
if req.HafCoinAmount < 0 {
|
||||
return ErrInvalidHafCoin
|
||||
}
|
||||
@@ -229,6 +237,85 @@ func validateRequest(req CreateRequest, rules publishRules) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func consumableValue(summary map[string]any) float64 {
|
||||
if summary == nil {
|
||||
return 0
|
||||
}
|
||||
rawResources, ok := summary["resources"]
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
resources, ok := rawResources.([]any)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
total := 0.0
|
||||
for _, raw := range resources {
|
||||
resource, ok := raw.(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
mode, _ := resource["mode"].(string)
|
||||
if strings.TrimSpace(mode) != "收费" {
|
||||
continue
|
||||
}
|
||||
quantity := readSummaryFloat(resource["quantity"])
|
||||
if quantity <= 0 {
|
||||
continue
|
||||
}
|
||||
priceText, _ := resource["price"].(string)
|
||||
total += quantity * readUnitPrice(priceText)
|
||||
}
|
||||
return roundMoney(total)
|
||||
}
|
||||
|
||||
func readSummaryFloat(value any) float64 {
|
||||
switch current := value.(type) {
|
||||
case float64:
|
||||
return current
|
||||
case int:
|
||||
return float64(current)
|
||||
case int64:
|
||||
return float64(current)
|
||||
case json.Number:
|
||||
parsed, err := current.Float64()
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return parsed
|
||||
case string:
|
||||
parsed, err := strconv.ParseFloat(strings.TrimSpace(current), 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return parsed
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func readUnitPrice(priceText string) float64 {
|
||||
numbers := priceNumberPattern.FindAllString(priceText, -1)
|
||||
if len(numbers) == 0 {
|
||||
return 0
|
||||
}
|
||||
amount, err := strconv.ParseFloat(numbers[0], 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
if len(numbers) >= 2 {
|
||||
count, err := strconv.ParseFloat(numbers[1], 64)
|
||||
if err == nil && count > 0 {
|
||||
return amount / count
|
||||
}
|
||||
}
|
||||
return amount
|
||||
}
|
||||
|
||||
func roundMoney(value float64) float64 {
|
||||
return math.Round(value*100) / 100
|
||||
}
|
||||
|
||||
func readFireLevel(summary map[string]any) (int, bool) {
|
||||
if summary == nil {
|
||||
return 0, false
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package listing
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestConsumableValueOnlyCountsChargedResources(t *testing.T) {
|
||||
value := consumableValue(map[string]any{
|
||||
"resources": []any{
|
||||
map[string]any{"mode": "收费", "quantity": float64(5), "price": "0.1元/个"},
|
||||
map[string]any{"mode": "赠送", "quantity": float64(3), "price": "0.6元/个"},
|
||||
map[string]any{"mode": "收费", "quantity": float64(2), "price": "1元/2个"},
|
||||
},
|
||||
})
|
||||
|
||||
if value != 1.5 {
|
||||
t.Fatalf("expected 1.5, got %.2f", value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateRequestRequiresDepositAboveConsumables(t *testing.T) {
|
||||
req := CreateRequest{
|
||||
Title: "测试账号",
|
||||
ServerRegion: "烽火地带",
|
||||
Price: 100,
|
||||
DepositAmount: 1.5,
|
||||
HafCoinAmount: 1000000,
|
||||
ScreenshotURLS: []string{"https://example.com/a.png"},
|
||||
AssetSummary: map[string]any{
|
||||
"resources": []any{
|
||||
map[string]any{"mode": "收费", "quantity": float64(2), "price": "1元/个"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if err := validateRequest(req, publishRules{}); err != ErrDepositTooLow {
|
||||
t.Fatalf("expected ErrDepositTooLow, got %v", err)
|
||||
}
|
||||
|
||||
req.DepositAmount = 2.01
|
||||
if err := validateRequest(req, publishRules{}); err != nil {
|
||||
t.Fatalf("expected valid request, got %v", err)
|
||||
}
|
||||
}
|
||||
@@ -20,9 +20,8 @@ type OrderDTO struct {
|
||||
LoginPlatform string `json:"login_platform"`
|
||||
RentedAt *time.Time `json:"rented_at"`
|
||||
EstimatedDurationHours int `json:"estimated_duration_hours"`
|
||||
RentStartAt *time.Time `json:"rent_start_at"`
|
||||
RentEndAt *time.Time `json:"rent_end_at"`
|
||||
RentAmount float64 `json:"rent_amount"`
|
||||
OwnerRentAmount float64 `json:"owner_rent_amount"`
|
||||
DepositAmount float64 `json:"deposit_amount"`
|
||||
PlatformFee float64 `json:"platform_fee"`
|
||||
AccountSnapshot datatypes.JSON `json:"account_snapshot"`
|
||||
@@ -90,6 +89,8 @@ type CheckoutDTO struct {
|
||||
InitiatedBy uint64 `json:"initiated_by"`
|
||||
Status string `json:"status"`
|
||||
RentAmount float64 `json:"rent_amount"`
|
||||
OwnerRentAmount float64 `json:"owner_rent_amount"`
|
||||
PlatformFee float64 `json:"platform_fee"`
|
||||
DepositAmount float64 `json:"deposit_amount"`
|
||||
ConsumableAmount float64 `json:"consumable_amount"`
|
||||
CoinConsumedM float64 `json:"coin_consumed_m"`
|
||||
|
||||
@@ -28,31 +28,81 @@ func NewRepository(db *gorm.DB) *Repository {
|
||||
return &Repository{db: db}
|
||||
}
|
||||
|
||||
func listingOrderPrice(listing model.RentalListing) float64 {
|
||||
switch {
|
||||
case listing.Price > 0:
|
||||
return listing.Price
|
||||
case listing.PriceDaily > 0:
|
||||
return listing.PriceDaily
|
||||
case listing.PriceHourly > 0:
|
||||
return listing.PriceHourly * float64(internalOrderHours)
|
||||
case listing.PriceWeekly > 0:
|
||||
return listing.PriceWeekly / 7
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
type orderPricing struct {
|
||||
RentAmount float64
|
||||
OwnerRentAmount float64
|
||||
PlatformFee float64
|
||||
}
|
||||
|
||||
func orderDurationHours(order model.RentalOrder) int {
|
||||
if order.EstimatedDurationHours > 0 {
|
||||
return order.EstimatedDurationHours
|
||||
}
|
||||
if order.RentHours > 0 {
|
||||
return order.RentHours
|
||||
}
|
||||
return internalOrderHours
|
||||
}
|
||||
|
||||
func buildOrderPricing(listing model.RentalListing, account model.GameAccount) orderPricing {
|
||||
rentAmount := roundMoney(listing.Price)
|
||||
ownerRentAmount := readSnapshotPrice(account.AssetSummary, "seller_total_price")
|
||||
if ownerRentAmount <= 0 || ownerRentAmount > rentAmount {
|
||||
ownerRentAmount = rentAmount
|
||||
}
|
||||
platformFee := readSnapshotPrice(account.AssetSummary, "platform_markup_amount")
|
||||
if platformFee <= 0 || roundMoney(ownerRentAmount+platformFee) != rentAmount {
|
||||
platformFee = roundMoney(rentAmount - ownerRentAmount)
|
||||
}
|
||||
if platformFee < 0 {
|
||||
platformFee = 0
|
||||
}
|
||||
return orderPricing{
|
||||
RentAmount: rentAmount,
|
||||
OwnerRentAmount: roundMoney(ownerRentAmount),
|
||||
PlatformFee: roundMoney(platformFee),
|
||||
}
|
||||
}
|
||||
|
||||
func readSnapshotPrice(raw datatypes.JSON, key string) float64 {
|
||||
if len(raw) == 0 {
|
||||
return 0
|
||||
}
|
||||
var summary map[string]any
|
||||
if err := json.Unmarshal(raw, &summary); err != nil {
|
||||
return 0
|
||||
}
|
||||
breakdown, ok := summary["price_breakdown"].(map[string]any)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
return readJSONNumber(breakdown[key])
|
||||
}
|
||||
|
||||
func readJSONNumber(value any) float64 {
|
||||
switch typed := value.(type) {
|
||||
case float64:
|
||||
return typed
|
||||
case float32:
|
||||
return float64(typed)
|
||||
case int:
|
||||
return float64(typed)
|
||||
case int64:
|
||||
return float64(typed)
|
||||
case json.Number:
|
||||
number, err := typed.Float64()
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return number
|
||||
case string:
|
||||
number, err := strconv.ParseFloat(typed, 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return number
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Repository) Create(renterID uint64, req CreateRequest) (*OrderDTO, error) {
|
||||
var createdID uint64
|
||||
err := r.db.Transaction(func(tx *gorm.DB) error {
|
||||
@@ -80,6 +130,7 @@ func (r *Repository) Create(renterID uint64, req CreateRequest) (*OrderDTO, erro
|
||||
return err
|
||||
}
|
||||
rentHours := internalOrderHours
|
||||
pricing := buildOrderPricing(listing, account)
|
||||
order := model.RentalOrder{
|
||||
OrderNo: orderNo,
|
||||
ListingID: listing.ID,
|
||||
@@ -87,10 +138,10 @@ func (r *Repository) Create(renterID uint64, req CreateRequest) (*OrderDTO, erro
|
||||
OwnerID: listing.OwnerID,
|
||||
RenterID: renterID,
|
||||
EstimatedDurationHours: rentHours,
|
||||
RentHours: rentHours,
|
||||
RentAmount: listingOrderPrice(listing),
|
||||
RentAmount: pricing.RentAmount,
|
||||
OwnerRentAmount: pricing.OwnerRentAmount,
|
||||
DepositAmount: listing.DepositAmount,
|
||||
PlatformFee: 0,
|
||||
PlatformFee: pricing.PlatformFee,
|
||||
AccountSnapshot: snapshot,
|
||||
Status: "pending_payment",
|
||||
HandoffStatus: "none",
|
||||
@@ -370,10 +421,7 @@ func (r *Repository) ConfirmReceive(userID uint64, orderID uint64) error {
|
||||
order.HandoffStatus = "received"
|
||||
durationHours := orderDurationHours(order)
|
||||
order.EstimatedDurationHours = durationHours
|
||||
order.RentStartAt = &now
|
||||
order.RentedAt = &now
|
||||
rentEnd := now.Add(time.Duration(durationHours) * time.Hour)
|
||||
order.RentEndAt = &rentEnd
|
||||
orderID := order.ID
|
||||
if err := notification.Append(tx, notification.Entry{
|
||||
UserID: order.OwnerID,
|
||||
@@ -983,13 +1031,15 @@ func buildCheckout(order model.RentalOrder, initiatedBy uint64, status string, c
|
||||
InitiatedBy: initiatedBy,
|
||||
Status: status,
|
||||
RentAmount: order.RentAmount,
|
||||
OwnerRentAmount: order.OwnerRentAmount,
|
||||
PlatformFee: order.PlatformFee,
|
||||
DepositAmount: order.DepositAmount,
|
||||
ConsumableAmount: roundMoney(consumableAmount),
|
||||
CoinConsumedM: roundMoney(coinConsumedM),
|
||||
OtherAmount: roundMoney(otherAmount),
|
||||
DepositDeductAmount: roundMoney(deductAmount),
|
||||
RenterRefundAmount: roundMoney(renterRefund),
|
||||
OwnerIncomeAmount: roundMoney(order.RentAmount + deductAmount),
|
||||
OwnerIncomeAmount: roundMoney(order.OwnerRentAmount + deductAmount),
|
||||
Content: content,
|
||||
EvidenceURLS: evidence,
|
||||
}, nil
|
||||
@@ -1068,15 +1118,7 @@ type orderRow struct {
|
||||
|
||||
func (row orderRow) toDTO() OrderDTO {
|
||||
rentedAt := row.RentedAt
|
||||
if rentedAt == nil {
|
||||
rentedAt = row.RentStartAt
|
||||
}
|
||||
durationHours := orderDurationHours(row.RentalOrder)
|
||||
rentEndAt := row.RentEndAt
|
||||
if rentEndAt == nil && rentedAt != nil && durationHours > 0 {
|
||||
calculatedEnd := rentedAt.Add(time.Duration(durationHours) * time.Hour)
|
||||
rentEndAt = &calculatedEnd
|
||||
}
|
||||
return OrderDTO{
|
||||
ID: row.ID,
|
||||
OrderNo: row.OrderNo,
|
||||
@@ -1091,9 +1133,8 @@ func (row orderRow) toDTO() OrderDTO {
|
||||
LoginPlatform: row.LoginPlatform,
|
||||
RentedAt: rentedAt,
|
||||
EstimatedDurationHours: durationHours,
|
||||
RentStartAt: row.RentStartAt,
|
||||
RentEndAt: rentEndAt,
|
||||
RentAmount: row.RentAmount,
|
||||
OwnerRentAmount: row.OwnerRentAmount,
|
||||
DepositAmount: row.DepositAmount,
|
||||
PlatformFee: row.PlatformFee,
|
||||
AccountSnapshot: row.AccountSnapshot,
|
||||
@@ -1126,6 +1167,8 @@ func toCheckoutDTO(checkout model.OrderCheckout) CheckoutDTO {
|
||||
InitiatedBy: checkout.InitiatedBy,
|
||||
Status: checkout.Status,
|
||||
RentAmount: checkout.RentAmount,
|
||||
OwnerRentAmount: checkout.OwnerRentAmount,
|
||||
PlatformFee: checkout.PlatformFee,
|
||||
DepositAmount: checkout.DepositAmount,
|
||||
ConsumableAmount: checkout.ConsumableAmount,
|
||||
CoinConsumedM: checkout.CoinConsumedM,
|
||||
|
||||
Reference in New Issue
Block a user