238 lines
7.9 KiB
Go
238 lines
7.9 KiB
Go
package order
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"hfb_sys/backend/internal/model"
|
|
"hfb_sys/backend/internal/modules/notification"
|
|
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/clause"
|
|
)
|
|
|
|
func (r *Repository) SubmitCheckout(ctx context.Context, userID uint64, orderID uint64, req SubmitCheckoutRequest) (*HandoffRecordDTO, error) {
|
|
var recordID uint64
|
|
err := r.db.WithContext(ctx).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 != orderStatusRenting && order.Status != orderStatusOverdue) || (order.HandoffStatus != handoffStatusReceived && order.HandoffStatus != handoffStatusReturnOverdue) {
|
|
return ErrCheckoutCannotSubmit
|
|
}
|
|
hasOpen, err := hasOpenCheckout(tx, order.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if hasOpen {
|
|
return ErrCheckoutCannotSubmit
|
|
}
|
|
record := model.HandoffRecord{
|
|
OrderID: order.ID,
|
|
FromUserID: order.RenterID,
|
|
ToUserID: order.OwnerID,
|
|
Type: "renter_checkout",
|
|
Content: req.Content,
|
|
}
|
|
if err := tx.Create(&record).Error; err != nil {
|
|
return err
|
|
}
|
|
checkout, err := buildCheckout(order, order.RenterID, checkoutStatusSubmitted, req.Content, req.EvidenceURLS, req.ConsumableAmountCent, req.CoinConsumedM, req.OtherAmountCent, 0, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := tx.Create(&checkout).Error; err != nil {
|
|
return err
|
|
}
|
|
order.Status = orderStatusPendingCheckoutConfirm
|
|
order.HandoffStatus = handoffStatusPendingOwnerCheckout
|
|
order.SettlementStatus = settlementStatusPending
|
|
now := time.Now()
|
|
order.HandoffStartedAt = &now
|
|
orderID := order.ID
|
|
if err := notification.Append(tx, notification.Entry{
|
|
UserID: order.OwnerID,
|
|
Type: "checkout",
|
|
Title: "租客已发起结账",
|
|
Content: "请检查账号状态和消耗明细,确认无误后完成结算。",
|
|
BizType: "order",
|
|
BizID: &orderID,
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
if err := tx.Save(&order).Error; err != nil {
|
|
return err
|
|
}
|
|
recordID = record.ID
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return r.findHandoffRecord(ctx, recordID)
|
|
}
|
|
|
|
func (r *Repository) ConfirmReturn(ctx context.Context, userID uint64, orderID uint64) error {
|
|
return r.ConfirmCheckout(ctx, userID, orderID)
|
|
}
|
|
|
|
func (r *Repository) ConfirmCheckout(ctx context.Context, userID uint64, orderID uint64) error {
|
|
var refund *refundAction
|
|
err := r.db.WithContext(ctx).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.OwnerID != userID {
|
|
return ErrPermissionDenied
|
|
}
|
|
if order.Status != orderStatusPendingCheckoutConfirm || order.HandoffStatus != handoffStatusPendingOwnerCheckout {
|
|
return ErrCheckoutCannotConfirm
|
|
}
|
|
var checkout model.OrderCheckout
|
|
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
|
|
Where("order_id = ? AND status = ?", order.ID, checkoutStatusSubmitted).
|
|
Order("id DESC").
|
|
First(&checkout).Error; err != nil {
|
|
return err
|
|
}
|
|
now := time.Now()
|
|
if err := tx.Model(&model.HandoffRecord{}).
|
|
Where("order_id = ? AND type = ?", order.ID, "renter_checkout").
|
|
Update("confirmed_by_owner_at", now).Error; err != nil {
|
|
return err
|
|
}
|
|
checkout.Status = checkoutStatusAccepted
|
|
checkout.OwnerAdjustedAt = &now
|
|
action, err := r.finalizeCheckout(tx, &order, &checkout, "号主已确认结账,订单完成。")
|
|
refund = action
|
|
return err
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
r.startRefundBestEffort(ctx, refund)
|
|
return nil
|
|
}
|
|
|
|
func (r *Repository) CounterCheckout(ctx context.Context, userID uint64, orderID uint64, req CounterCheckoutRequest) (*CheckoutDTO, error) {
|
|
var checkoutID uint64
|
|
err := r.db.WithContext(ctx).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.OwnerID != userID {
|
|
return ErrPermissionDenied
|
|
}
|
|
if order.Status != orderStatusPendingCheckoutConfirm || order.HandoffStatus != handoffStatusPendingOwnerCheckout {
|
|
return ErrCheckoutCannotCounter
|
|
}
|
|
var checkout model.OrderCheckout
|
|
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
|
|
Where("order_id = ? AND status = ?", order.ID, checkoutStatusSubmitted).
|
|
Order("id DESC").
|
|
First(&checkout).Error; err != nil {
|
|
return err
|
|
}
|
|
// 号主修正只认押金赔付扣除;other 与 deduct 对齐,避免双字段语义分裂
|
|
depositDeductCent := req.DepositDeductAmountCent
|
|
if depositDeductCent <= 0 && req.OtherAmountCent > 0 {
|
|
depositDeductCent = req.OtherAmountCent
|
|
}
|
|
next, err := buildCheckout(order, checkout.InitiatedBy, checkoutStatusCountered, checkout.Content, req.EvidenceURLS, req.ConsumableAmountCent, req.CoinConsumedM, depositDeductCent, depositDeductCent, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
now := time.Now()
|
|
checkout.Status = checkoutStatusCountered
|
|
checkout.RentAmountCent = next.RentAmountCent
|
|
checkout.OwnerRentAmountCent = next.OwnerRentAmountCent
|
|
checkout.PlatformFeeCent = next.PlatformFeeCent
|
|
checkout.DepositAmountCent = next.DepositAmountCent
|
|
checkout.ConsumableAmountCent = next.ConsumableAmountCent
|
|
checkout.CoinConsumedM = next.CoinConsumedM
|
|
checkout.OtherAmountCent = next.OtherAmountCent
|
|
checkout.DepositDeductAmountCent = next.DepositDeductAmountCent
|
|
checkout.RenterRefundAmountCent = next.RenterRefundAmountCent
|
|
checkout.OwnerIncomeAmountCent = next.OwnerIncomeAmountCent
|
|
checkout.OwnerAdjustmentReason = req.Reason
|
|
checkout.OwnerAdjustedAt = &now
|
|
checkout.EvidenceURLS = next.EvidenceURLS
|
|
order.Status = orderStatusPendingCheckoutAccept
|
|
order.HandoffStatus = handoffStatusPendingRenterCheckout
|
|
order.SettlementStatus = settlementStatusPending
|
|
orderID := order.ID
|
|
if err := notification.Append(tx, notification.Entry{
|
|
UserID: order.RenterID,
|
|
Type: "checkout",
|
|
Title: "号主已修改结账金额",
|
|
Content: "请核对号主修正的消耗和结算金额。同意后订单完成;不同意可发起争议。",
|
|
BizType: "order",
|
|
BizID: &orderID,
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
if err := tx.Save(&order).Error; err != nil {
|
|
return err
|
|
}
|
|
if err := tx.Save(&checkout).Error; err != nil {
|
|
return err
|
|
}
|
|
checkoutID = checkout.ID
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
checkout, err := r.findCheckout(ctx, checkoutID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dto := toCheckoutDTOForUser(*checkout, userID, model.RentalOrder{
|
|
OwnerID: userID,
|
|
RentAmountCent: checkout.RentAmountCent,
|
|
OwnerRentAmountCent: checkout.OwnerRentAmountCent,
|
|
DepositAmountCent: checkout.DepositAmountCent,
|
|
})
|
|
return &dto, nil
|
|
}
|
|
|
|
func (r *Repository) AcceptCheckout(ctx context.Context, userID uint64, orderID uint64) error {
|
|
var refund *refundAction
|
|
err := r.db.WithContext(ctx).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 != orderStatusPendingCheckoutAccept || order.HandoffStatus != handoffStatusPendingRenterCheckout {
|
|
return ErrCheckoutCannotConfirm
|
|
}
|
|
var checkout model.OrderCheckout
|
|
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
|
|
Where("order_id = ? AND status = ?", order.ID, checkoutStatusCountered).
|
|
Order("id DESC").
|
|
First(&checkout).Error; err != nil {
|
|
return err
|
|
}
|
|
now := time.Now()
|
|
checkout.Status = checkoutStatusAccepted
|
|
checkout.RenterConfirmedAt = &now
|
|
action, err := r.finalizeCheckout(tx, &order, &checkout, "租客已确认修正结账,订单完成。")
|
|
refund = action
|
|
return err
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
r.startRefundBestEffort(ctx, refund)
|
|
return nil
|
|
}
|