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) CancelByOrder(ctx context.Context, userID uint64, orderID uint64) (*DisputeDTO, error) { var row model.Dispute if err := r.db.WithContext(ctx). Where("order_id = ? AND initiator_id = ? AND status IN ?", orderID, userID, []string{"open", "processing"}). Order("id DESC"). First(&row).Error; err != nil { return nil, err } return r.Cancel(ctx, userID, row.ID) } func (r *Repository) Cancel(ctx context.Context, userID uint64, id uint64) (*DisputeDTO, error) { err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { var row model.Dispute if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&row, id).Error; err != nil { return err } if row.InitiatorID != userID { return ErrPermissionDenied } if row.Status != "open" && row.Status != "processing" { return ErrDisputeCannotCancel } var order model.RentalOrder if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&order, row.OrderID).Error; err != nil { return err } if err := restoreOrderAfterDisputeCancel(tx, &row, &order); err != nil { return err } now := time.Now() row.Status = "cancelled" row.ArbitrationRemark = "用户自行取消申诉" row.HandledAt = &now if err := tx.Save(&row).Error; err != nil { return err } if err := tx.Save(&order).Error; err != nil { return err } recordType := "dispute_cancelled" recordContent := "用户自行取消申诉,订单已恢复到申诉前流程。" if row.Type == "checkout_dispute" { recordType = "checkout_dispute_cancelled" recordContent = "用户自行取消结账争议,订单已恢复到申诉前流程。" } record := model.HandoffRecord{ OrderID: order.ID, FromUserID: row.InitiatorID, ToUserID: row.TargetUserID, Type: recordType, Content: recordContent, } if err := tx.Create(&record).Error; err != nil { return err } disputeID := row.ID if err := notification.Append(tx, notification.Entry{ UserID: row.TargetUserID, Type: "dispute", Title: "申诉已取消", Content: "对方已自行取消申诉,订单已恢复到申诉前流程。", BizType: "dispute", BizID: &disputeID, }, notification.Entry{ UserID: row.InitiatorID, Type: "dispute", Title: "申诉已取消", Content: "你已取消申诉,订单已恢复到申诉前流程。", BizType: "dispute", BizID: &disputeID, }, ); err != nil { return err } return nil }) if err != nil { return nil, err } var row disputeRow if err := r.baseQuery(ctx).Where("d.id = ?", id).First(&row).Error; err != nil { return nil, err } dto := row.toDTO() return &dto, nil } func restoreOrderAfterDisputeCancel(tx *gorm.DB, row *model.Dispute, order *model.RentalOrder) error { if row.Type == "checkout_dispute" { return restoreCheckoutDispute(tx, row, order) } if order.Status != "disputing" { return ErrDisputeCannotCancel } restoreOrderStatusFromSnapshot(row, order) if order.Status == "" || order.Status == "disputing" { order.Status = inferOrderStatusForDisputeCancel(*order) } if order.Status == "" { return ErrDisputeCannotCancel } return nil } func restoreCheckoutDispute(tx *gorm.DB, row *model.Dispute, order *model.RentalOrder) error { if order.Status != "checkout_disputing" { return ErrDisputeCannotCancel } var checkout model.OrderCheckout query := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("order_id = ? AND status = ?", order.ID, "disputed") if row.CheckoutID != nil && *row.CheckoutID > 0 { query = query.Where("id = ?", *row.CheckoutID) } if err := query.Order("id DESC").First(&checkout).Error; err != nil { return err } restoreOrderStatusFromSnapshot(row, order) if order.Status == "" || order.Status == "checkout_disputing" { order.Status = "pending_checkout_confirm" order.HandoffStatus = "pending_owner_checkout" if checkout.OwnerAdjustedAt != nil || checkout.OwnerAdjustmentReason != "" { order.Status = "pending_checkout_accept" order.HandoffStatus = "pending_renter_checkout" } order.SettlementStatus = "pending" } checkout.Status = row.PreviousCheckoutStatus if checkout.Status == "" { checkout.Status = "submitted" if order.Status == "pending_checkout_accept" { checkout.Status = "countered" } } checkout.RenterRejectedAt = nil if err := tx.Save(&checkout).Error; err != nil { return err } return nil } func restoreOrderStatusFromSnapshot(row *model.Dispute, order *model.RentalOrder) { if row.PreviousOrderStatus != "" { order.Status = row.PreviousOrderStatus } if row.PreviousHandoffStatus != "" { order.HandoffStatus = row.PreviousHandoffStatus } if row.PreviousSettlementStatus != "" { order.SettlementStatus = row.PreviousSettlementStatus } } func inferOrderStatusForDisputeCancel(order model.RentalOrder) string { switch order.HandoffStatus { case "none": return "pending_payment" case "pending_owner", "pending_renter_confirm": return "pending_handoff" case "received": return "renting" case "return_overdue": return "overdue" default: return "" } }