package dispute import ( "context" "time" "hfb_sys/backend/internal/model" "hfb_sys/backend/internal/modules/notification" "gorm.io/gorm" "gorm.io/gorm/clause" ) func (r *Repository) Create(ctx context.Context, userID uint64, orderID uint64, req CreateRequest) (*DisputeDTO, error) { var createdID 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 && order.OwnerID != userID { return ErrPermissionDenied } if order.Status == "completed" || order.Status == "cancelled" || order.Status == "closed" { return ErrInvalidDispute } isCheckoutDispute := order.Status == "pending_checkout_confirm" || order.Status == "pending_checkout_accept" var count int64 if err := tx.Model(&model.Dispute{}). Where("order_id = ? AND status IN ?", order.ID, []string{"open", "processing"}). Count(&count).Error; err != nil { return err } if count > 0 { return ErrDisputeExists } targetID := order.OwnerID if userID == order.OwnerID { targetID = order.RenterID } evidence, err := marshalEvidence(req.EvidenceURLS) if err != nil { return err } row := model.Dispute{ OrderID: order.ID, InitiatorID: userID, TargetUserID: targetID, Type: disputeType(req.Type, isCheckoutDispute), Status: "open", Description: req.Description, EvidenceURLS: evidence, PreviousOrderStatus: order.Status, PreviousHandoffStatus: order.HandoffStatus, PreviousSettlementStatus: order.SettlementStatus, } var checkout model.OrderCheckout if isCheckoutDispute { if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}). Where("order_id = ? AND status IN ?", order.ID, []string{"submitted", "countered"}). Order("id DESC"). First(&checkout).Error; err != nil { return err } row.CheckoutID = &checkout.ID row.PreviousCheckoutStatus = checkout.Status } if err := tx.Create(&row).Error; err != nil { return err } if isCheckoutDispute { now := time.Now() order.Status = "checkout_disputing" order.HandoffStatus = "checkout_disputed" order.SettlementStatus = "disputed" updates := map[string]any{ "status": "disputed", "updated_at": now, } if userID == order.RenterID { updates["renter_rejected_at"] = now } if err := tx.Model(&model.OrderCheckout{}). Where("id = ?", checkout.ID). Updates(updates).Error; err != nil { return err } } else { order.Status = "disputing" } recordType := "dispute_opened" recordContent := "用户发起订单申诉:" + req.Description if isCheckoutDispute { recordType = "checkout_dispute_opened" recordContent = "用户发起结账争议:" + req.Description } record := model.HandoffRecord{ OrderID: order.ID, FromUserID: userID, ToUserID: targetID, Type: recordType, Content: recordContent, } if err := tx.Create(&record).Error; err != nil { return err } if err := tx.Save(&order).Error; err != nil { return err } disputeID := row.ID title := "订单进入申诉" content := "对方已发起申诉,请等待客服仲裁或补充沟通记录。" if isCheckoutDispute { title = "订单进入结账争议" content = "对方已发起结账争议,请等待客服仲裁或补充结账证据。" } if err := notification.Append(tx, notification.Entry{ UserID: targetID, Type: "dispute", Title: title, Content: content, BizType: "dispute", BizID: &disputeID, }, notification.Entry{ UserID: userID, Type: "dispute", Title: "申诉已提交", Content: "申诉已进入待处理状态,客服仲裁后会通知双方。", BizType: "dispute", BizID: &disputeID, }, ); err != nil { return err } createdID = row.ID return nil }) if err != nil { return nil, err } return r.FindForUser(ctx, userID, createdID) } func disputeType(input string, isCheckoutDispute bool) string { if isCheckoutDispute { return "checkout_dispute" } return input }