538 lines
19 KiB
Go
538 lines
19 KiB
Go
package order
|
||
|
||
import (
|
||
"context"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
"hfb_sys/backend/internal/model"
|
||
"hfb_sys/backend/internal/modules/distribution"
|
||
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
func (r *Repository) ListForUser(ctx context.Context, userID uint64, page, pageSize int, siteCode ...string) (*UserPaginatedResult, error) {
|
||
page, pageSize = normalizePagination(page, pageSize)
|
||
ownerVisible := "NOT (o.owner_id = ? AND o.owner_hidden_at IS NOT NULL)"
|
||
base := r.baseQuery(ctx).Where("o.renter_id = ? OR o.owner_id = ?", userID, userID).Where(ownerVisible, userID)
|
||
countBase := r.db.WithContext(ctx).Table("rental_orders AS o").Where("o.renter_id = ? OR o.owner_id = ?", userID, userID).Where(ownerVisible, userID)
|
||
|
||
var code string
|
||
if len(siteCode) > 0 {
|
||
code = strings.TrimSpace(siteCode[0])
|
||
}
|
||
if code != "" {
|
||
base = base.Joins("INNER JOIN distribution_sites AS ds ON ds.id = o.distribution_site_id AND ds.site_code = ?", code).
|
||
Where("o.owner_id = ? OR o.distribution_mode != ?", userID, distribution.ModeTwoPartyDistributorReceive)
|
||
countBase = countBase.Joins("INNER JOIN distribution_sites AS ds ON ds.id = o.distribution_site_id AND ds.site_code = ?", code).
|
||
Where("o.owner_id = ? OR o.distribution_mode != ?", userID, distribution.ModeTwoPartyDistributorReceive)
|
||
} else {
|
||
mainSiteCond := "o.owner_id = ? OR o.distribution_site_id IS NULL OR o.distribution_site_id = 0 OR o.distribution_mode = ?"
|
||
base = base.Where(mainSiteCond, userID, distribution.ModeTwoPartyDistributorReceive)
|
||
countBase = countBase.Where(mainSiteCond, userID, distribution.ModeTwoPartyDistributorReceive)
|
||
}
|
||
|
||
var total int64
|
||
if err := countBase.Count(&total).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
var rows []orderRow
|
||
db := r.db.WithContext(ctx)
|
||
err := base.
|
||
Order("o.id DESC").
|
||
Offset((page - 1) * pageSize).
|
||
Limit(pageSize).
|
||
Scan(&rows).Error
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
ownerOrderIDs := make([]uint64, 0, len(rows))
|
||
for _, row := range rows {
|
||
if row.OwnerID == userID {
|
||
ownerOrderIDs = append(ownerOrderIDs, row.ID)
|
||
}
|
||
}
|
||
signals, err := loadOwnerHideSignals(db, ownerOrderIDs)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
items := make([]UserOrderListItemDTO, 0, len(rows))
|
||
paymentTimeoutMinutes := pendingPaymentTimeoutMinutes(db)
|
||
for _, row := range rows {
|
||
var deadline *time.Time
|
||
if row.Status == orderStatusPendingPayment && paymentTimeoutMinutes > 0 {
|
||
value := row.CreatedAt.Add(time.Duration(paymentTimeoutMinutes) * time.Minute)
|
||
deadline = &value
|
||
}
|
||
item := row.toUserListItem(userID, deadline)
|
||
if item.Role == "owner" {
|
||
_, disputed := signals.disputed[row.ID]
|
||
item.CanHide = ownerMayHideOrder(row.RentalOrder, disputed, signals.dist[row.ID])
|
||
}
|
||
items = append(items, item)
|
||
}
|
||
return &UserPaginatedResult{Items: items, Total: total, Page: page, PageSize: pageSize}, nil
|
||
}
|
||
|
||
func (r *Repository) ListSellerHandoffs(ctx context.Context, userID uint64, query SellerHandoffQuery) (*SellerHandoffPaginatedResult, error) {
|
||
page, pageSize := normalizePagination(query.Page, query.PageSize)
|
||
base := r.baseQuery(ctx).
|
||
Where("o.owner_id = ?", userID).
|
||
Where("o.owner_hidden_at IS NULL").
|
||
Where("o.status IN ?", sellerHandoffStatuses())
|
||
if query.Status != "" {
|
||
base = base.Where("o.status = ?", query.Status)
|
||
}
|
||
if query.SiteCode != "" {
|
||
base = base.Joins("INNER JOIN distribution_sites AS ds ON ds.id = o.distribution_site_id AND ds.site_code = ?", query.SiteCode)
|
||
}
|
||
|
||
var total int64
|
||
if err := base.Session(&gorm.Session{}).Count(&total).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
var rows []orderRow
|
||
if err := base.Order("o.id DESC").Offset((page - 1) * pageSize).Limit(pageSize).Scan(&rows).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
items := make([]UserOrderListItemDTO, 0, len(rows))
|
||
for _, row := range rows {
|
||
items = append(items, row.toUserListItem(userID, nil))
|
||
}
|
||
metrics, err := r.sellerHandoffMetrics(ctx, userID, query.SiteCode)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return &SellerHandoffPaginatedResult{
|
||
Items: items,
|
||
Total: total,
|
||
Page: page,
|
||
PageSize: pageSize,
|
||
Metrics: metrics,
|
||
}, nil
|
||
}
|
||
|
||
func (r *Repository) sellerHandoffMetrics(ctx context.Context, userID uint64, siteCode ...string) (SellerHandoffMetrics, error) {
|
||
base := r.db.WithContext(ctx).Table("rental_orders AS o").Where("o.owner_id = ? AND o.owner_hidden_at IS NULL", userID)
|
||
if len(siteCode) > 0 && siteCode[0] != "" {
|
||
base = base.Joins("INNER JOIN distribution_sites AS ds ON ds.id = o.distribution_site_id AND ds.site_code = ?", siteCode[0])
|
||
}
|
||
var metrics SellerHandoffMetrics
|
||
if err := base.Session(&gorm.Session{}).Where("o.status = ? AND o.handoff_status = ?", orderStatusPendingHandoff, handoffStatusPendingOwner).Count(&metrics.PendingHandoff).Error; err != nil {
|
||
return SellerHandoffMetrics{}, err
|
||
}
|
||
if err := base.Session(&gorm.Session{}).Where("o.status = ?", orderStatusPendingCheckoutConfirm).Count(&metrics.PendingCheckout).Error; err != nil {
|
||
return SellerHandoffMetrics{}, err
|
||
}
|
||
if err := base.Session(&gorm.Session{}).Where("o.status IN ?", sellerHandoffAbnormalStatuses()).Count(&metrics.Abnormal).Error; err != nil {
|
||
return SellerHandoffMetrics{}, err
|
||
}
|
||
return metrics, nil
|
||
}
|
||
|
||
func sellerHandoffStatuses() []string {
|
||
return []string{
|
||
orderStatusPendingHandoff,
|
||
orderStatusRenting,
|
||
orderStatusOverdue,
|
||
orderStatusPendingCheckoutConfirm,
|
||
orderStatusPendingCheckoutAccept,
|
||
orderStatusCheckoutDisputing,
|
||
"disputing",
|
||
orderStatusAbnormal,
|
||
}
|
||
}
|
||
|
||
func sellerHandoffAbnormalStatuses() []string {
|
||
return []string{orderStatusOverdue, orderStatusCheckoutDisputing, "disputing", orderStatusAbnormal}
|
||
}
|
||
|
||
func isSellerHandoffStatus(status string) bool {
|
||
for _, candidate := range sellerHandoffStatuses() {
|
||
if status == candidate {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
func (r *Repository) ListAdmin(ctx context.Context, query AdminOrderQuery) (*AdminOrderListPaginatedResult, error) {
|
||
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
|
||
}
|
||
|
||
var total int64
|
||
// 首页查询且返回条数小于 pageSize 时,总数等于当前返回条数,免除第二次 COUNT(*) 扫描
|
||
if page == 1 && len(rows) < pageSize {
|
||
total = int64(len(rows))
|
||
} else {
|
||
countDB := applyAdminOrderFilters(r.adminOrderCountQuery(ctx, query), query)
|
||
if err := countDB.Count(&total).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
}
|
||
|
||
// 号主实际收入:按需收集本页行号,一次性批量查询(单条 SQL),避免逐行查询的 N+1。
|
||
ownerIncome := make(map[uint64]int64)
|
||
incomeOrderIDs := make([]uint64, 0, len(rows))
|
||
for _, row := range rows {
|
||
if adminListItemNeedsOwnerIncome(row) {
|
||
incomeOrderIDs = append(incomeOrderIDs, row.ID)
|
||
}
|
||
}
|
||
if len(incomeOrderIDs) > 0 {
|
||
ownerIncome, err = r.ownerActualIncomeByOrder(ctx, incomeOrderIDs)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
}
|
||
|
||
items := make([]AdminOrderListItemDTO, 0, len(rows))
|
||
for _, row := range rows {
|
||
item := row.toAdminListItem()
|
||
if amount, ok := ownerIncome[row.ID]; ok {
|
||
value := amount
|
||
item.OwnerIncomeAmountCent = &value
|
||
}
|
||
items = append(items, item)
|
||
}
|
||
|
||
return &AdminOrderListPaginatedResult{
|
||
Items: items,
|
||
Total: total,
|
||
Page: page,
|
||
PageSize: pageSize,
|
||
}, nil
|
||
}
|
||
|
||
func normalizePagination(page, pageSize int) (int, int) {
|
||
if page < 1 {
|
||
page = 1
|
||
}
|
||
if pageSize < 1 {
|
||
pageSize = 20
|
||
}
|
||
if pageSize > 100 {
|
||
pageSize = 100
|
||
}
|
||
return page, pageSize
|
||
}
|
||
|
||
func (r *Repository) adminOrderCountQuery(ctx context.Context, query AdminOrderQuery) *gorm.DB {
|
||
kw := strings.TrimSpace(query.Keyword)
|
||
upper := strings.ToUpper(kw)
|
||
needsJoin := false
|
||
if kw != "" {
|
||
if isPhoneKeyword(kw) || strings.HasPrefix(upper, "RO") || strings.HasPrefix(upper, "ORD") {
|
||
needsJoin = false
|
||
} else if id, err := strconv.ParseUint(kw, 10, 64); err == nil && id > 0 && kw[0] != '0' && len(kw) < 9 {
|
||
needsJoin = false
|
||
} else {
|
||
needsJoin = true
|
||
}
|
||
}
|
||
if !needsJoin {
|
||
return r.db.WithContext(ctx).Table("rental_orders AS o")
|
||
}
|
||
return r.adminOrderJoinQuery(ctx)
|
||
}
|
||
|
||
// ownerActualIncomeByOrder 批量读取每单号主最终实际结算金额(分)。
|
||
//
|
||
// 结账行只能代表协商结果,不能代表仲裁后的最终落账:
|
||
// - 平台代管订单:最终金额写入 rental_orders.offline_settlement_amount_cent;
|
||
// - 普通钱包订单:最终金额以 wallet_ledger 中号主的实际入账合计为准。
|
||
//
|
||
// 仲裁可能不更新历史结账行,甚至 dispute.checkout_id 为空,因此不能继续取
|
||
// order_checkouts 的 owner_income_amount_cent,否则列表会显示仲裁前金额。
|
||
func (r *Repository) ownerActualIncomeByOrder(ctx context.Context, orderIDs []uint64) (map[uint64]int64, error) {
|
||
result := make(map[uint64]int64, len(orderIDs))
|
||
if len(orderIDs) == 0 {
|
||
return result, nil
|
||
}
|
||
var rows []struct {
|
||
ID uint64
|
||
OwnerIncomeAmountCent int64
|
||
}
|
||
if err := r.db.WithContext(ctx).
|
||
Table("rental_orders AS o").
|
||
Select(`o.id,
|
||
CASE WHEN o.settlement_mode = 'platform_managed'
|
||
THEN COALESCE(o.offline_settlement_amount_cent, 0)
|
||
ELSE COALESCE(SUM(wl.amount_cent), 0)
|
||
END AS owner_income_amount_cent`).
|
||
Joins(`LEFT JOIN wallet_ledger AS wl
|
||
ON wl.order_id = o.id
|
||
AND wl.user_id = o.owner_id
|
||
AND wl.direction = 'in'
|
||
AND wl.biz_type IN ?`, []string{walletBizOwnerIncome, walletBizDepositCompensation, "arbitration_owner_income"}).
|
||
Where("o.id IN ?", orderIDs).
|
||
Group("o.id, o.settlement_mode, o.offline_settlement_amount_cent").
|
||
Scan(&rows).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
for _, row := range rows {
|
||
result[row.ID] = row.OwnerIncomeAmountCent
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
// adminListItemNeedsOwnerIncome 判断列表行是否需要附带号主实际收入。
|
||
// 仅已结账/已仲裁时库中金额为最终值;协商中的金额会随后续还价变化,
|
||
// 不作为列表结论展示(协商中的实时金额走详情接口)。
|
||
func adminListItemNeedsOwnerIncome(row orderRow) bool {
|
||
return row.SettlementStatus == settlementStatusSettled ||
|
||
row.SettlementStatus == settlementStatusArbitrated
|
||
}
|
||
|
||
func applyAdminOrderFilters(db *gorm.DB, query AdminOrderQuery) *gorm.DB {
|
||
if query.UserID > 0 {
|
||
// 精确命中该用户作为号主或租客参与的订单,避免与 keyword 的数字 ID 模糊命中混淆。
|
||
db = db.Where("(o.owner_id = ? OR o.renter_id = ?)", query.UserID, query.UserID)
|
||
}
|
||
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 != "" {
|
||
upper := strings.ToUpper(keyword)
|
||
if isPhoneKeyword(keyword) {
|
||
// 先由 users(phone) 唯一索引解析用户,再让订单表 owner_id/renter_id 索引驱动。
|
||
// 不能直接写 owner.phone/renter.phone:跨表别名 OR 会让 MySQL 倒序扫描全部订单。
|
||
db = db.Where(
|
||
"(o.owner_id = (SELECT id FROM users WHERE phone = ?) OR o.renter_id = (SELECT id FROM users WHERE phone = ?))",
|
||
keyword,
|
||
keyword,
|
||
)
|
||
} else if strings.HasPrefix(upper, "RO") || strings.HasPrefix(upper, "ORD") {
|
||
// 订单号:命中 rental_orders(order_no) 唯一索引
|
||
db = db.Where("o.order_no = ?", keyword)
|
||
} else if strings.HasPrefix(upper, "L") || (len(keyword) >= 12 && isAllDigits(keyword)) {
|
||
// 商品编号(如 L前缀或时间计算生成的 12位纯数字如 202609040184):命中 rental_listings(listing_no) 唯一索引
|
||
db = db.Where("l.listing_no = ?", keyword)
|
||
} else if id, err := strconv.ParseUint(keyword, 10, 64); err == nil && id > 0 && keyword[0] != '0' && id < 100000000 {
|
||
// 短纯数字 ID(非零开头):命中 rental_orders(id) 主键及关联 ID
|
||
db = db.Where("(o.id = ? OR o.listing_id = ? OR o.owner_id = ? OR o.renter_id = ?)", id, id, id, id)
|
||
} else {
|
||
like := "%" + keyword + "%"
|
||
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, siteCode ...string) (*OrderDTO, error) {
|
||
var row orderRow
|
||
db := r.db.WithContext(ctx)
|
||
query := r.baseQuery(ctx).
|
||
Where("o.id = ? AND (o.renter_id = ? OR o.owner_id = ?)", orderID, userID, userID).
|
||
Where("NOT (o.owner_id = ? AND o.owner_hidden_at IS NOT NULL)", userID)
|
||
|
||
var code string
|
||
if len(siteCode) > 0 {
|
||
code = strings.TrimSpace(siteCode[0])
|
||
}
|
||
if code != "" {
|
||
query = query.Joins("LEFT JOIN distribution_sites AS ds ON ds.id = o.distribution_site_id").
|
||
Where("o.owner_id = ? OR (ds.site_code = ? AND o.distribution_mode != ?)", userID, code, distribution.ModeTwoPartyDistributorReceive)
|
||
} else {
|
||
query = query.Where("o.owner_id = ? OR o.distribution_site_id IS NULL OR o.distribution_site_id = 0 OR o.distribution_mode = ?", userID, distribution.ModeTwoPartyDistributorReceive)
|
||
}
|
||
|
||
if err := query.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 (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
|
||
}
|
||
|
||
func isAllDigits(s string) bool {
|
||
for i := 0; i < len(s); i++ {
|
||
if s[i] < '0' || s[i] > '9' {
|
||
return false
|
||
}
|
||
}
|
||
return len(s) > 0
|
||
}
|
||
|
||
func isPhoneKeyword(s string) bool {
|
||
return len(s) == 11 && s[0] == '1' && isAllDigits(s)
|
||
}
|