286 lines
8.7 KiB
Go
286 lines
8.7 KiB
Go
package order
|
|
|
|
import (
|
|
"context"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"hfb_sys/backend/internal/model"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
func (r *Repository) ListForUser(ctx context.Context, userID uint64) ([]OrderDTO, error) {
|
|
var rows []orderRow
|
|
db := r.db.WithContext(ctx)
|
|
err := r.baseQuery(ctx).
|
|
Where("o.renter_id = ? OR o.owner_id = ?", userID, userID).
|
|
Order("o.id DESC").
|
|
Scan(&rows).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
items := make([]OrderDTO, 0, len(rows))
|
|
paymentTimeoutMinutes := pendingPaymentTimeoutMinutes(db)
|
|
for _, row := range rows {
|
|
dto := row.toDTOForUser(userID)
|
|
applyPaymentDeadline(&dto, row.RentalOrder, paymentTimeoutMinutes)
|
|
if shouldAttachCheckout(row.Status) {
|
|
dto.Checkout = r.latestCheckoutDTOForUser(ctx, row.ID, userID, row.RentalOrder)
|
|
}
|
|
items = append(items, dto)
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
func (r *Repository) ListAdmin(ctx context.Context, query AdminOrderQuery) (*PaginatedResult, error) {
|
|
var total int64
|
|
db := r.db.WithContext(ctx)
|
|
countDB := applyAdminOrderFilters(r.adminOrderJoinQuery(ctx), query)
|
|
if err := countDB.Count(&total).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
page := query.Page
|
|
pageSize := query.PageSize
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
if pageSize < 1 {
|
|
pageSize = 20
|
|
}
|
|
offset := (page - 1) * pageSize
|
|
var rows []orderRow
|
|
err := applyAdminOrderFilters(r.adminQuery(ctx), query).
|
|
Order("o.id DESC").
|
|
Limit(pageSize).
|
|
Offset(offset).
|
|
Scan(&rows).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
items := make([]OrderDTO, 0, len(rows))
|
|
paymentTimeoutMinutes := pendingPaymentTimeoutMinutes(db)
|
|
for _, row := range rows {
|
|
dto := row.toAdminDTO()
|
|
applyPaymentDeadline(&dto, row.RentalOrder, paymentTimeoutMinutes)
|
|
items = append(items, dto)
|
|
}
|
|
|
|
return &PaginatedResult{
|
|
Items: items,
|
|
Total: total,
|
|
Page: page,
|
|
PageSize: pageSize,
|
|
}, nil
|
|
}
|
|
|
|
func applyAdminOrderFilters(db *gorm.DB, query AdminOrderQuery) *gorm.DB {
|
|
if query.Status != "" {
|
|
db = db.Where("o.status = ?", query.Status)
|
|
}
|
|
if query.HandoffStatus != "" {
|
|
db = db.Where("o.handoff_status = ?", query.HandoffStatus)
|
|
}
|
|
switch query.HandoffMode {
|
|
case handoffModePlatform:
|
|
db = db.Where("(o.handoff_mode = ? OR o.settlement_mode = ?)", handoffModePlatform, settlementModePlatformManaged)
|
|
case handoffModeOwner, "normal":
|
|
db = db.Where("COALESCE(NULLIF(o.handoff_mode, ''), ?) <> ? AND COALESCE(NULLIF(o.settlement_mode, ''), ?) <> ?", handoffModeOwner, handoffModePlatform, settlementModeOwnerWallet, settlementModePlatformManaged)
|
|
}
|
|
if query.SettlementStatus != "" {
|
|
db = db.Where("o.settlement_status = ?", query.SettlementStatus)
|
|
}
|
|
if query.OfflineSettlementStatus != "" {
|
|
db = db.Where("COALESCE(NULLIF(o.offline_settlement_status, ''), 'none') = ?", query.OfflineSettlementStatus)
|
|
}
|
|
if keyword := strings.TrimSpace(query.Keyword); keyword != "" {
|
|
like := "%" + keyword + "%"
|
|
if id, err := strconv.ParseUint(keyword, 10, 64); err == nil {
|
|
db = db.Where(
|
|
`(o.order_no LIKE ? OR l.listing_no LIKE ? OR a.title LIKE ? OR owner.phone LIKE ? OR renter.phone LIKE ? OR o.id = ? OR o.listing_id = ? OR o.owner_id = ? OR o.renter_id = ?)`,
|
|
like,
|
|
like,
|
|
like,
|
|
like,
|
|
like,
|
|
id,
|
|
id,
|
|
id,
|
|
id,
|
|
)
|
|
} else {
|
|
db = db.Where(
|
|
`(o.order_no LIKE ? OR l.listing_no LIKE ? OR a.title LIKE ? OR owner.phone LIKE ? OR renter.phone LIKE ?)`,
|
|
like,
|
|
like,
|
|
like,
|
|
like,
|
|
like,
|
|
)
|
|
}
|
|
}
|
|
return db
|
|
}
|
|
|
|
func (r *Repository) FindAdmin(ctx context.Context, orderID uint64) (*OrderDTO, error) {
|
|
var row orderRow
|
|
db := r.db.WithContext(ctx)
|
|
if err := r.adminQuery(ctx).Where("o.id = ?", orderID).First(&row).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
dto := row.toAdminDTO()
|
|
applyPaymentDeadline(&dto, row.RentalOrder, pendingPaymentTimeoutMinutes(db))
|
|
dto.ActiveDispute = r.activeDisputeDTO(ctx, orderID)
|
|
dto.Checkout = r.latestCheckoutAdminDTO(ctx, orderID)
|
|
return &dto, nil
|
|
}
|
|
|
|
func (r *Repository) FindLatestAdminByListing(ctx context.Context, listingID uint64) (*OrderDTO, error) {
|
|
var row orderRow
|
|
db := r.db.WithContext(ctx)
|
|
if err := r.adminQuery(ctx).
|
|
Where("o.listing_id = ?", listingID).
|
|
Order("o.id DESC").
|
|
First(&row).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
dto := row.toAdminDTO()
|
|
applyPaymentDeadline(&dto, row.RentalOrder, pendingPaymentTimeoutMinutes(db))
|
|
dto.ActiveDispute = r.activeDisputeDTO(ctx, row.ID)
|
|
dto.Checkout = r.latestCheckoutAdminDTO(ctx, row.ID)
|
|
return &dto, nil
|
|
}
|
|
|
|
func (r *Repository) FindForUser(ctx context.Context, userID uint64, orderID uint64) (*OrderDTO, error) {
|
|
var row orderRow
|
|
db := r.db.WithContext(ctx)
|
|
if err := r.baseQuery(ctx).
|
|
Where("o.id = ? AND (o.renter_id = ? OR o.owner_id = ?)", orderID, userID, userID).
|
|
First(&row).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
dto := row.toDTOForUser(userID)
|
|
applyPaymentDeadline(&dto, row.RentalOrder, pendingPaymentTimeoutMinutes(db))
|
|
dto.ActiveDispute = r.activeDisputeDTO(ctx, orderID)
|
|
dto.Checkout = r.latestCheckoutDTOForUser(ctx, orderID, userID, row.RentalOrder)
|
|
return &dto, nil
|
|
}
|
|
|
|
func pendingPaymentTimeoutMinutes(tx *gorm.DB) int {
|
|
var row model.SystemConfig
|
|
err := tx.Where("`key` = ?", "order.pending_payment_timeout_minutes").First(&row).Error
|
|
if err != nil {
|
|
return defaultPendingPaymentTimeoutMinutes
|
|
}
|
|
value, err := strconv.Atoi(row.Value)
|
|
if err != nil || value < 0 {
|
|
return defaultPendingPaymentTimeoutMinutes
|
|
}
|
|
return value
|
|
}
|
|
|
|
func applyPaymentDeadline(dto *OrderDTO, order model.RentalOrder, timeoutMinutes int) {
|
|
if dto == nil || order.Status != orderStatusPendingPayment || timeoutMinutes <= 0 {
|
|
return
|
|
}
|
|
deadline := order.CreatedAt.Add(time.Duration(timeoutMinutes) * time.Minute)
|
|
dto.PaymentDeadlineAt = &deadline
|
|
}
|
|
|
|
func (r *Repository) latestCheckoutAdminDTO(ctx context.Context, orderID uint64) *CheckoutDTO {
|
|
var checkout model.OrderCheckout
|
|
if err := r.db.WithContext(ctx).Where("order_id = ?", orderID).Order("id DESC").First(&checkout).Error; err != nil {
|
|
return nil
|
|
}
|
|
dto := toCheckoutAdminDTO(checkout)
|
|
var order model.RentalOrder
|
|
if err := r.db.WithContext(ctx).First(&order, orderID).Error; err == nil {
|
|
refreshCheckoutSettlementDTO(&dto, order, checkout)
|
|
}
|
|
return &dto
|
|
}
|
|
|
|
func (r *Repository) activeDisputeDTO(ctx context.Context, orderID uint64) *ActiveDisputeDTO {
|
|
var row model.Dispute
|
|
if err := r.db.WithContext(ctx).
|
|
Where("order_id = ? AND status IN ?", orderID, []string{"open", "processing"}).
|
|
Order("id DESC").
|
|
First(&row).Error; err != nil {
|
|
return nil
|
|
}
|
|
return &ActiveDisputeDTO{
|
|
ID: row.ID,
|
|
InitiatorID: row.InitiatorID,
|
|
InitiatorType: effectiveDisputeInitiatorType(row),
|
|
InitiatorAdminID: row.InitiatorAdminID,
|
|
Type: row.Type,
|
|
Status: row.Status,
|
|
}
|
|
}
|
|
|
|
func effectiveDisputeInitiatorType(row model.Dispute) string {
|
|
if row.InitiatorType == "admin" {
|
|
return "admin"
|
|
}
|
|
return "user"
|
|
}
|
|
|
|
func shouldAttachCheckout(status string) bool {
|
|
switch status {
|
|
case orderStatusPendingCheckoutConfirm, orderStatusPendingCheckoutAccept, orderStatusCheckoutDisputing, orderStatusCompleted:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func (r *Repository) latestCheckoutDTOForUser(ctx context.Context, orderID uint64, userID uint64, order model.RentalOrder) *CheckoutDTO {
|
|
var checkout model.OrderCheckout
|
|
if err := r.db.WithContext(ctx).Where("order_id = ?", orderID).Order("id DESC").First(&checkout).Error; err != nil {
|
|
return nil
|
|
}
|
|
dto := toCheckoutDTOForUser(checkout, userID, order)
|
|
return &dto
|
|
}
|
|
|
|
func (r *Repository) findCheckout(ctx context.Context, id uint64) (*model.OrderCheckout, error) {
|
|
var checkout model.OrderCheckout
|
|
if err := r.db.WithContext(ctx).First(&checkout, id).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return &checkout, nil
|
|
}
|
|
|
|
func (r *Repository) baseQuery(ctx context.Context) *gorm.DB {
|
|
return r.db.WithContext(ctx).Table("rental_orders AS o").
|
|
Select("o.*, l.listing_no, a.title, a.server_region, a.login_platform").
|
|
Joins("JOIN rental_listings AS l ON l.id = o.listing_id").
|
|
Joins("JOIN game_accounts AS a ON a.id = o.account_id")
|
|
}
|
|
|
|
func (r *Repository) adminQuery(ctx context.Context) *gorm.DB {
|
|
return r.adminOrderJoinQuery(ctx).
|
|
Select("o.*, l.listing_no, a.title, a.server_region, a.login_platform, owner.phone AS owner_phone, renter.phone AS renter_phone")
|
|
}
|
|
|
|
func (r *Repository) adminOrderJoinQuery(ctx context.Context) *gorm.DB {
|
|
return r.db.WithContext(ctx).Table("rental_orders AS o").
|
|
Joins("JOIN rental_listings AS l ON l.id = o.listing_id").
|
|
Joins("JOIN game_accounts AS a ON a.id = o.account_id").
|
|
Joins("JOIN users AS owner ON owner.id = o.owner_id").
|
|
Joins("JOIN users AS renter ON renter.id = o.renter_id")
|
|
}
|
|
|
|
type orderRow struct {
|
|
model.RentalOrder
|
|
Title string
|
|
ListingNo string
|
|
ServerRegion string
|
|
LoginPlatform string
|
|
OwnerPhone string
|
|
RenterPhone string
|
|
}
|