兼容接入订单租期字段

This commit is contained in:
yml
2026-05-24 18:24:39 +08:00
parent e69d08ca9f
commit 778e08a261
9 changed files with 211 additions and 85 deletions
+5 -1
View File
@@ -296,8 +296,12 @@ func (j *Job) handleRenterConfirmTimeout(ctx context.Context, now time.Time, cfg
func (j *Job) handleReturnOverdue(ctx context.Context, now time.Time, cfg thresholds) (int, error) {
var rows []model.RentalOrder
overdueBefore := now.Add(-time.Duration(cfg.ReturnOverdueGraceMinutes) * time.Minute)
err := j.db.WithContext(ctx).
Where("status = ? AND rent_end_at IS NOT NULL AND rent_end_at <= ?", "renting", now.Add(-time.Duration(cfg.ReturnOverdueGraceMinutes)*time.Minute)).
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).
Order("id ASC").
Limit(100).
Find(&rows).Error
+22 -20
View File
@@ -7,26 +7,28 @@ import (
)
type RentalOrder struct {
ID uint64 `gorm:"primaryKey" json:"id"`
OrderNo string `gorm:"size:64;not null;uniqueIndex" json:"order_no"`
ListingID uint64 `gorm:"not null;index" json:"listing_id"`
AccountID uint64 `gorm:"not null;index" json:"account_id"`
OwnerID uint64 `gorm:"not null;index" json:"owner_id"`
RenterID uint64 `gorm:"not null;index" json:"renter_id"`
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"`
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"`
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"`
SettledAt *time.Time `json:"settled_at"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
ID uint64 `gorm:"primaryKey" json:"id"`
OrderNo string `gorm:"size:64;not null;uniqueIndex" json:"order_no"`
ListingID uint64 `gorm:"not null;index" json:"listing_id"`
AccountID uint64 `gorm:"not null;index" json:"account_id"`
OwnerID uint64 `gorm:"not null;index" json:"owner_id"`
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"`
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"`
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"`
SettledAt *time.Time `json:"settled_at"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
func (RentalOrder) TableName() string {
+25 -23
View File
@@ -7,29 +7,31 @@ import (
)
type OrderDTO struct {
ID uint64 `json:"id"`
OrderNo string `json:"order_no"`
ListingID uint64 `json:"listing_id"`
AccountID uint64 `json:"account_id"`
OwnerID uint64 `json:"owner_id"`
RenterID uint64 `json:"renter_id"`
OwnerPhone string `json:"owner_phone,omitempty"`
RenterPhone string `json:"renter_phone,omitempty"`
Title string `json:"title"`
ServerRegion string `json:"server_region"`
LoginPlatform string `json:"login_platform"`
RentStartAt *time.Time `json:"rent_start_at"`
RentEndAt *time.Time `json:"rent_end_at"`
RentAmount float64 `json:"rent_amount"`
DepositAmount float64 `json:"deposit_amount"`
PlatformFee float64 `json:"platform_fee"`
AccountSnapshot datatypes.JSON `json:"account_snapshot"`
Status string `json:"status"`
HandoffStatus string `json:"handoff_status"`
SettlementStatus string `json:"settlement_status"`
Checkout *CheckoutDTO `json:"checkout,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
ID uint64 `json:"id"`
OrderNo string `json:"order_no"`
ListingID uint64 `json:"listing_id"`
AccountID uint64 `json:"account_id"`
OwnerID uint64 `json:"owner_id"`
RenterID uint64 `json:"renter_id"`
OwnerPhone string `json:"owner_phone,omitempty"`
RenterPhone string `json:"renter_phone,omitempty"`
Title string `json:"title"`
ServerRegion string `json:"server_region"`
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"`
DepositAmount float64 `json:"deposit_amount"`
PlatformFee float64 `json:"platform_fee"`
AccountSnapshot datatypes.JSON `json:"account_snapshot"`
Status string `json:"status"`
HandoffStatus string `json:"handoff_status"`
SettlementStatus string `json:"settlement_status"`
Checkout *CheckoutDTO `json:"checkout,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type CreateRequest struct {
+62 -36
View File
@@ -43,6 +43,16 @@ func listingOrderPrice(listing model.RentalListing) float64 {
}
}
func orderDurationHours(order model.RentalOrder) int {
if order.EstimatedDurationHours > 0 {
return order.EstimatedDurationHours
}
if order.RentHours > 0 {
return order.RentHours
}
return internalOrderHours
}
func (r *Repository) Create(renterID uint64, req CreateRequest) (*OrderDTO, error) {
var createdID uint64
err := r.db.Transaction(func(tx *gorm.DB) error {
@@ -71,19 +81,20 @@ func (r *Repository) Create(renterID uint64, req CreateRequest) (*OrderDTO, erro
}
rentHours := internalOrderHours
order := model.RentalOrder{
OrderNo: orderNo,
ListingID: listing.ID,
AccountID: listing.AccountID,
OwnerID: listing.OwnerID,
RenterID: renterID,
RentHours: rentHours,
RentAmount: listingOrderPrice(listing),
DepositAmount: listing.DepositAmount,
PlatformFee: 0,
AccountSnapshot: snapshot,
Status: "pending_payment",
HandoffStatus: "none",
SettlementStatus: "unsettled",
OrderNo: orderNo,
ListingID: listing.ID,
AccountID: listing.AccountID,
OwnerID: listing.OwnerID,
RenterID: renterID,
EstimatedDurationHours: rentHours,
RentHours: rentHours,
RentAmount: listingOrderPrice(listing),
DepositAmount: listing.DepositAmount,
PlatformFee: 0,
AccountSnapshot: snapshot,
Status: "pending_payment",
HandoffStatus: "none",
SettlementStatus: "unsettled",
}
if err := tx.Create(&order).Error; err != nil {
return err
@@ -357,8 +368,11 @@ func (r *Repository) ConfirmReceive(userID uint64, orderID uint64) error {
}
order.Status = "renting"
order.HandoffStatus = "received"
durationHours := orderDurationHours(order)
order.EstimatedDurationHours = durationHours
order.RentStartAt = &now
rentEnd := now.Add(time.Duration(order.RentHours) * time.Hour)
order.RentedAt = &now
rentEnd := now.Add(time.Duration(durationHours) * time.Hour)
order.RentEndAt = &rentEnd
orderID := order.ID
if err := notification.Append(tx, notification.Entry{
@@ -1053,29 +1067,41 @@ 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,
ListingID: row.ListingID,
AccountID: row.AccountID,
OwnerID: row.OwnerID,
RenterID: row.RenterID,
OwnerPhone: row.OwnerPhone,
RenterPhone: row.RenterPhone,
Title: row.Title,
ServerRegion: row.ServerRegion,
LoginPlatform: row.LoginPlatform,
RentStartAt: row.RentStartAt,
RentEndAt: row.RentEndAt,
RentAmount: row.RentAmount,
DepositAmount: row.DepositAmount,
PlatformFee: row.PlatformFee,
AccountSnapshot: row.AccountSnapshot,
Status: row.Status,
HandoffStatus: row.HandoffStatus,
SettlementStatus: row.SettlementStatus,
CreatedAt: row.CreatedAt,
UpdatedAt: row.UpdatedAt,
ID: row.ID,
OrderNo: row.OrderNo,
ListingID: row.ListingID,
AccountID: row.AccountID,
OwnerID: row.OwnerID,
RenterID: row.RenterID,
OwnerPhone: row.OwnerPhone,
RenterPhone: row.RenterPhone,
Title: row.Title,
ServerRegion: row.ServerRegion,
LoginPlatform: row.LoginPlatform,
RentedAt: rentedAt,
EstimatedDurationHours: durationHours,
RentStartAt: row.RentStartAt,
RentEndAt: rentEndAt,
RentAmount: row.RentAmount,
DepositAmount: row.DepositAmount,
PlatformFee: row.PlatformFee,
AccountSnapshot: row.AccountSnapshot,
Status: row.Status,
HandoffStatus: row.HandoffStatus,
SettlementStatus: row.SettlementStatus,
CreatedAt: row.CreatedAt,
UpdatedAt: row.UpdatedAt,
}
}
+4 -1
View File
@@ -78,6 +78,8 @@ CREATE TABLE rental_orders (
account_id BIGINT UNSIGNED NOT NULL,
owner_id BIGINT UNSIGNED NOT NULL,
renter_id BIGINT UNSIGNED NOT NULL,
rented_at DATETIME NULL,
estimated_duration_hours INT NOT NULL DEFAULT 24,
rent_start_at DATETIME NULL,
rent_end_at DATETIME NULL,
rent_hours INT NOT NULL,
@@ -95,7 +97,8 @@ CREATE TABLE rental_orders (
UNIQUE KEY uk_rental_orders_order_no (order_no),
KEY idx_rental_orders_renter_status (renter_id, status),
KEY idx_rental_orders_owner_status (owner_id, status),
KEY idx_rental_orders_listing_id (listing_id)
KEY idx_rental_orders_listing_id (listing_id),
KEY idx_rental_orders_rented_at (status, rented_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE handoff_records (
@@ -41,6 +41,67 @@ SET price = CASE
END
WHERE price <= 0;
SET @order_rented_at_exists := (
SELECT COUNT(*)
FROM information_schema.columns
WHERE table_schema = DATABASE()
AND table_name = 'rental_orders'
AND column_name = 'rented_at'
);
SET @add_order_rented_at_sql := IF(
@order_rented_at_exists = 0,
'ALTER TABLE rental_orders ADD COLUMN rented_at DATETIME NULL AFTER renter_id',
'SELECT 1'
);
PREPARE add_order_rented_at_stmt FROM @add_order_rented_at_sql;
EXECUTE add_order_rented_at_stmt;
DEALLOCATE PREPARE add_order_rented_at_stmt;
SET @order_estimated_duration_exists := (
SELECT COUNT(*)
FROM information_schema.columns
WHERE table_schema = DATABASE()
AND table_name = 'rental_orders'
AND column_name = 'estimated_duration_hours'
);
SET @add_order_estimated_duration_sql := IF(
@order_estimated_duration_exists = 0,
'ALTER TABLE rental_orders ADD COLUMN estimated_duration_hours INT NOT NULL DEFAULT 24 AFTER rented_at',
'SELECT 1'
);
PREPARE add_order_estimated_duration_stmt FROM @add_order_estimated_duration_sql;
EXECUTE add_order_estimated_duration_stmt;
DEALLOCATE PREPARE add_order_estimated_duration_stmt;
UPDATE rental_orders
SET
rented_at = COALESCE(rented_at, rent_start_at),
estimated_duration_hours = CASE
WHEN estimated_duration_hours > 0 THEN estimated_duration_hours
WHEN rent_hours > 0 THEN rent_hours
ELSE 24
END
WHERE rented_at IS NULL OR estimated_duration_hours <= 0;
SET @order_rented_at_index_exists := (
SELECT COUNT(*)
FROM information_schema.statistics
WHERE table_schema = DATABASE()
AND table_name = 'rental_orders'
AND index_name = 'idx_rental_orders_rented_at'
);
SET @add_order_rented_at_index_sql := IF(
@order_rented_at_index_exists = 0,
'ALTER TABLE rental_orders ADD KEY idx_rental_orders_rented_at (status, rented_at)',
'SELECT 1'
);
PREPARE add_order_rented_at_index_stmt FROM @add_order_rented_at_index_sql;
EXECUTE add_order_rented_at_index_stmt;
DEALLOCATE PREPARE add_order_rented_at_index_stmt;
INSERT INTO system_configs (`key`, `value`, description)
VALUES ('order.pending_payment_timeout_minutes', '15', '订单待支付超时取消分钟数')
ON DUPLICATE KEY UPDATE `key` = VALUES(`key`);