153 lines
4.2 KiB
Go
153 lines
4.2 KiB
Go
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
|
|
}
|