Files

144 lines
4.7 KiB
Go

package order
import (
"context"
"strings"
"time"
"hfb_sys/backend/internal/model"
"hfb_sys/backend/internal/modules/notification"
"hfb_sys/backend/internal/processlog"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
func canAdminForceHandoff(order model.RentalOrder) bool {
if isPlatformHandoffOrder(order) {
if order.Status == orderStatusPendingHandoff {
return order.HandoffStatus == handoffStatusPendingRenterConfirm
}
return order.Status == orderStatusAbnormal && order.HandoffStatus == handoffStatusRenterConfirmTimeout
}
if isPlatformSettlementOrder(order) {
return false
}
if order.Status == orderStatusPendingHandoff {
return order.HandoffStatus == handoffStatusPendingOwner ||
order.HandoffStatus == handoffStatusOwnerTimeout ||
order.HandoffStatus == handoffStatusPendingRenterConfirm
}
return order.Status == orderStatusAbnormal && order.HandoffStatus == handoffStatusRenterConfirmTimeout
}
func forceHandoffRecordType(order model.RentalOrder) string {
if isPlatformHandoffOrder(order) {
return "platform_handoff"
}
return "owner_handoff"
}
func forceHandoffNeedsContent(order model.RentalOrder) bool {
return order.HandoffStatus == handoffStatusPendingOwner || order.HandoffStatus == handoffStatusOwnerTimeout
}
// AdminForceHandoff 由客服确认订单已完成交接,并立即开始租期。
func (r *Repository) AdminForceHandoff(ctx context.Context, adminID uint64, orderID uint64, req ForceHandoffRequest, meta AuditMeta) error {
return 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 !canAdminForceHandoff(order) || strings.TrimSpace(req.Reason) == "" {
return ErrOrderCannotForceHandoff
}
before := orderState(order)
if order.RefundStatus == refundStatusPending || order.RefundStatus == refundStatusPendingReview {
return ErrOrderCannotForceHandoff
}
var activeDisputeCount int64
if err := tx.Model(&model.Dispute{}).
Where("order_id = ? AND status IN ?", order.ID, []string{"open", "processing"}).
Count(&activeDisputeCount).Error; err != nil {
return err
}
if activeDisputeCount > 0 {
return ErrOrderCannotForceHandoff
}
needsContent := forceHandoffNeedsContent(order)
content := strings.TrimSpace(req.Content)
if needsContent && content == "" {
return ErrOrderCannotForceHandoff
}
if !needsContent {
var handoffCount int64
if err := tx.Model(&model.HandoffRecord{}).
Where("order_id = ? AND type = ?", order.ID, forceHandoffRecordType(order)).
Count(&handoffCount).Error; err != nil {
return err
}
if handoffCount == 0 {
return ErrOrderCannotForceHandoff
}
content = "客服已确认双方完成交接,订单已进入使用中。"
}
beforeOrderStatus := order.Status
beforeHandoffStatus := order.HandoffStatus
now := time.Now()
record := model.HandoffRecord{
OrderID: order.ID,
FromUserID: adminID,
ToUserID: order.RenterID,
Type: "admin_force_handoff",
Content: content,
}
if err := tx.Create(&record).Error; err != nil {
return err
}
order.Status = orderStatusRenting
order.HandoffStatus = handoffStatusReceived
order.EstimatedDurationHours = estimateOrderDurationHours(order.AccountSnapshot)
order.RentedAt = &now
if err := appendOrderEvent(tx, order, "handoff", "admin_force_handoff", processlog.ActorAdmin, adminID, processlog.ActorUser, &order.RenterID, content, req.Reason, nil, before, nil); err != nil {
return err
}
orderID := order.ID
if err := notification.Append(tx,
notification.Entry{
UserID: order.RenterID,
Type: "order_admin",
Title: "客服已确认交接",
Content: "客服已确认双方完成交接,订单已进入使用中,租期已开始计算。",
BizType: "order",
BizID: &orderID,
},
notification.Entry{
UserID: order.OwnerID,
Type: "order_admin",
Title: "客服已确认交接",
Content: "客服已确认双方完成交接,订单已进入使用中,租期已开始计算。",
BizType: "order",
BizID: &orderID,
},
); err != nil {
return err
}
if err := appendAuditLog(tx, adminID, "order.force_handoff", "order", order.ID, meta, map[string]any{
"order_id": order.ID,
"order_no": order.OrderNo,
"reason": strings.TrimSpace(req.Reason),
"content_length": len(content),
"before_order_status": beforeOrderStatus,
"after_order_status": order.Status,
"before_handoff_status": beforeHandoffStatus,
"after_handoff_status": order.HandoffStatus,
}); err != nil {
return err
}
return tx.Save(&order).Error
})
}