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 }) }