拆分订单仓储模块

This commit is contained in:
yml
2026-06-09 19:56:32 +08:00
parent ca6ad1e65c
commit 609b49f92b
10 changed files with 1777 additions and 1701 deletions
+316
View File
@@ -0,0 +1,316 @@
package order
import (
"time"
"hfb_sys/backend/internal/model"
"hfb_sys/backend/internal/modules/chat"
"hfb_sys/backend/internal/modules/notification"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
func (r *Repository) Create(renterID uint64, req CreateRequest) (*OrderDTO, error) {
var createdID uint64
err := r.db.Transaction(func(tx *gorm.DB) error {
var listing model.RentalListing
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&listing, req.ListingID).Error; err != nil {
return err
}
if listing.Status != "published" || listing.ReviewStatus != "approved" || listing.InTransaction {
return ErrListingUnavailable
}
if listing.OwnerID == renterID {
return ErrCannotRentOwnListing
}
var account model.GameAccount
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&account, listing.AccountID).Error; err != nil {
return err
}
snapshot, err := makeAccountSnapshot(account, listing)
if err != nil {
return err
}
orderNo, err := newOrderNo()
if err != nil {
return err
}
rentHours := internalOrderHours
pricing := buildOrderPricing(listing, account)
depositOriginalAmountCent := listing.DepositAmountCent
paidDepositAmountCent, waivedDepositAmountCent, err := r.depositAmountsForOrder(tx, renterID, depositOriginalAmountCent)
if err != nil {
return err
}
order := model.RentalOrder{
OrderNo: orderNo,
ListingID: listing.ID,
AccountID: listing.AccountID,
OwnerID: listing.OwnerID,
RenterID: renterID,
EstimatedDurationHours: rentHours,
RentAmountCent: pricing.RentAmountCent,
OwnerRentAmountCent: pricing.OwnerRentAmountCent,
DepositAmountCent: paidDepositAmountCent,
DepositOriginalAmountCent: depositOriginalAmountCent,
DepositWaivedAmountCent: waivedDepositAmountCent,
PlatformFeeCent: pricing.PlatformFeeCent,
AccountSnapshot: snapshot,
Status: "pending_payment",
HandoffStatus: "none",
SettlementStatus: "unsettled",
}
if err := tx.Create(&order).Error; err != nil {
return err
}
orderID := order.ID
if err := notification.Append(tx,
notification.Entry{
UserID: order.RenterID,
Type: "order",
Title: "订单已创建",
Content: "订单已创建,请在有效时间内完成支付。",
BizType: "order",
BizID: &orderID,
},
); err != nil {
return err
}
listing.InTransaction = true
if err := tx.Save(&listing).Error; err != nil {
return err
}
createdID = order.ID
return nil
})
if err != nil {
return nil, err
}
return r.FindForUser(renterID, createdID)
}
func (r *Repository) depositAmountsForOrder(tx *gorm.DB, renterID uint64, originalDepositCent int64) (int64, int64, error) {
if originalDepositCent <= 0 {
return 0, 0, nil
}
var user model.User
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&user, renterID).Error; err != nil {
return 0, 0, err
}
used, err := activeDepositFreeUsed(tx, renterID)
if err != nil {
return 0, 0, err
}
paidDeposit, waivedDeposit := calculateDepositWaiver(originalDepositCent, user.DepositFreeQuotaCent, used)
return paidDeposit, waivedDeposit, nil
}
func activeDepositFreeUsed(tx *gorm.DB, renterID uint64) (int64, error) {
var used int64
err := tx.Model(&model.RentalOrder{}).
Where("renter_id = ? AND status NOT IN ?",
renterID,
[]string{"completed", "cancelled", "closed"},
).
Select("COALESCE(SUM(deposit_waived_amount_cent), 0)").
Scan(&used).Error
return used, err
}
func calculateDepositWaiver(originalDepositCent int64, quotaCent int64, usedCent int64) (int64, int64) {
remaining := maxCent(quotaCent-usedCent, 0)
waived := minCent(originalDepositCent, remaining)
paid := maxCent(originalDepositCent-waived, 0)
return paid, waived
}
// Pay 保留旧接口兼容,但真实付款必须走 payment 模块的渠道支付入口。
func (r *Repository) Pay(userID uint64, orderID uint64) error {
return ErrChannelPaymentRequired
}
// ConfirmPaidFromChannel 在乐刷确认支付后推进订单状态;租客资金不进入站内钱包。
func (r *Repository) ConfirmPaidFromChannel(orderID uint64, providerBizNo string) error {
var newConvID uint64
err := r.db.Transaction(func(tx *gorm.DB) error {
var order model.RentalOrder
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&order, orderID).Error; err != nil {
return err
}
if order.Status == "pending_handoff" || order.Status == "renting" {
return nil
}
if order.Status != "pending_payment" {
return ErrOrderCannotPay
}
var listing model.RentalListing
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&listing, order.ListingID).Error; err != nil {
return err
}
if listing.Status != "published" || listing.ReviewStatus != "approved" || !listing.InTransaction {
return ErrListingUnavailable
}
var account model.GameAccount
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&account, order.AccountID).Error; err != nil {
return err
}
// 租客已通过外部渠道付款,这里不写租客钱包流水。
orderID := order.ID
order.Status = "pending_handoff"
order.HandoffStatus = "pending_owner"
listing.Status = "rented"
account.Status = "rented"
conv, err := chat.EnsureOrderConversation(tx, order)
if err != nil {
return err
}
newConvID = conv.ID
if err := notification.Append(tx,
notification.Entry{
UserID: order.OwnerID,
Type: "order",
Title: "收到新的租号订单",
Content: "租客已完成支付,请尽快提交交接说明。",
BizType: "order",
BizID: &orderID,
},
notification.Entry{
UserID: order.RenterID,
Type: "order",
Title: "订单支付成功",
Content: "支付已完成,等待号主提交交接说明。",
BizType: "order",
BizID: &orderID,
},
); err != nil {
return err
}
if err := tx.Save(&order).Error; err != nil {
return err
}
if err := tx.Save(&listing).Error; err != nil {
return err
}
return tx.Save(&account).Error
})
if err != nil {
return err
}
if newConvID > 0 && r.chatRepo != nil {
r.chatRepo.NotifyNewConversation(newConvID)
}
return nil
}
func (r *Repository) Cancel(userID uint64, orderID uint64) error {
var refund *refundAction
err := r.db.Transaction(func(tx *gorm.DB) error {
var order model.RentalOrder
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
Where("id = ? AND renter_id = ?", orderID, userID).
First(&order).Error; err != nil {
return err
}
if order.Status != "pending_payment" && order.Status != "pending_handoff" {
return ErrOrderCannotCancel
}
var listing model.RentalListing
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&listing, order.ListingID).Error; err != nil {
return err
}
var account model.GameAccount
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&account, order.AccountID).Error; err != nil {
return err
}
beforeStatus := order.Status
order.Status = "cancelled"
order.HandoffStatus = "cancelled"
orderID := order.ID
if beforeStatus == "pending_handoff" {
totalCent := order.RentAmountCent + order.DepositAmountCent
action, err := r.prepareRefund(&order, totalCent, "cancel_refund", "取消订单原路退款")
if err != nil {
return err
}
refund = action
}
if err := notification.Append(tx,
notification.Entry{
UserID: order.OwnerID,
Type: "order",
Title: "订单已取消",
Content: "租客已取消订单,账号已重新释放。",
BizType: "order",
BizID: &orderID,
},
notification.Entry{
UserID: order.RenterID,
Type: "order",
Title: "订单取消成功",
Content: "订单已取消,退款将原路退回您的支付账户。",
BizType: "order",
BizID: &orderID,
},
); err != nil {
return err
}
listing.Status = "published"
listing.InTransaction = false
account.Status = "published"
if err := tx.Save(&order).Error; err != nil {
return err
}
if err := tx.Save(&listing).Error; err != nil {
return err
}
return tx.Save(&account).Error
})
if err != nil {
return err
}
r.startRefundBestEffort(refund)
return nil
}
func (r *Repository) ConfirmReceive(userID uint64, orderID uint64) error {
return r.db.Transaction(func(tx *gorm.DB) error {
var order model.RentalOrder
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&order, orderID).Error; err != nil {
return err
}
if order.RenterID != userID {
return ErrPermissionDenied
}
if order.Status != "pending_handoff" || order.HandoffStatus != "pending_renter_confirm" {
return ErrOrderCannotReceive
}
now := time.Now()
if err := tx.Model(&model.HandoffRecord{}).
Where("order_id = ? AND type = ?", order.ID, "owner_handoff").
Update("confirmed_by_renter_at", now).Error; err != nil {
return err
}
order.Status = "renting"
order.HandoffStatus = "received"
durationHours := orderDurationHours(order)
order.EstimatedDurationHours = durationHours
order.RentedAt = &now
orderID := order.ID
if err := notification.Append(tx, notification.Entry{
UserID: order.OwnerID,
Type: "handoff",
Title: "租客已确认收号",
Content: "订单已进入使用中。",
BizType: "order",
BizID: &orderID,
}); err != nil {
return err
}
return tx.Save(&order).Error
})
}