支持客服主动发起申诉
This commit is contained in:
@@ -11,6 +11,14 @@ import (
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
const (
|
||||
disputeInitiatorUser = "user"
|
||||
disputeInitiatorAdmin = "admin"
|
||||
|
||||
disputeTargetOwner = "owner"
|
||||
disputeTargetRenter = "renter"
|
||||
)
|
||||
|
||||
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 {
|
||||
@@ -46,6 +54,7 @@ func (r *Repository) Create(ctx context.Context, userID uint64, orderID uint64,
|
||||
row := model.Dispute{
|
||||
OrderID: order.ID,
|
||||
InitiatorID: userID,
|
||||
InitiatorType: disputeInitiatorUser,
|
||||
TargetUserID: targetID,
|
||||
Type: disputeType(req.Type, isCheckoutDispute),
|
||||
Status: "open",
|
||||
@@ -154,9 +163,183 @@ func (r *Repository) Create(ctx context.Context, userID uint64, orderID uint64,
|
||||
return r.FindForUser(ctx, userID, createdID)
|
||||
}
|
||||
|
||||
func (r *Repository) AdminCreateByOrder(ctx context.Context, adminID uint64, orderID uint64, req AdminCreateRequest, meta AuditMeta) (*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.Status == "completed" || order.Status == "cancelled" || order.Status == "closed" {
|
||||
return ErrInvalidDispute
|
||||
}
|
||||
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, err := adminDisputeTargetUserID(order, req.TargetRole)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
evidence, err := marshalEvidence(req.EvidenceURLS)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
isCheckoutDispute := order.Status == "pending_checkout_confirm" || order.Status == "pending_checkout_accept"
|
||||
adminIDCopy := adminID
|
||||
row := model.Dispute{
|
||||
OrderID: order.ID,
|
||||
InitiatorID: 0,
|
||||
InitiatorType: disputeInitiatorAdmin,
|
||||
InitiatorAdminID: &adminIDCopy,
|
||||
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"
|
||||
if err := tx.Model(&model.OrderCheckout{}).
|
||||
Where("id = ?", checkout.ID).
|
||||
Updates(map[string]any{"status": "disputed", "updated_at": now}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
order.Status = "disputing"
|
||||
}
|
||||
recordType := "admin_dispute_opened"
|
||||
recordContent := "客服发起订单申诉:" + req.Description
|
||||
if isCheckoutDispute {
|
||||
recordType = "admin_checkout_dispute_opened"
|
||||
recordContent = "客服发起结账争议:" + req.Description
|
||||
}
|
||||
record := model.HandoffRecord{
|
||||
OrderID: order.ID,
|
||||
FromUserID: adminID,
|
||||
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 := appendAdminCreatedDisputeNotifications(tx, order, disputeID, title, content); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := appendAuditLog(tx, adminID, "dispute.admin_create", "dispute", row.ID, meta, map[string]any{
|
||||
"dispute_id": row.ID,
|
||||
"order_id": order.ID,
|
||||
"order_no": order.OrderNo,
|
||||
"type": row.Type,
|
||||
"target_role": req.TargetRole,
|
||||
"target_user_id": targetID,
|
||||
"description": req.Description,
|
||||
"previous_order_status": row.PreviousOrderStatus,
|
||||
"previous_handoff_status": row.PreviousHandoffStatus,
|
||||
"previous_settlement_status": row.PreviousSettlementStatus,
|
||||
"after_order_status": order.Status,
|
||||
"after_handoff_status": order.HandoffStatus,
|
||||
"after_settlement_status": order.SettlementStatus,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
createdID = row.ID
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r.FindAdminByID(ctx, createdID)
|
||||
}
|
||||
|
||||
func disputeType(input string, isCheckoutDispute bool) string {
|
||||
if isCheckoutDispute {
|
||||
return "checkout_dispute"
|
||||
}
|
||||
return input
|
||||
}
|
||||
|
||||
func effectiveInitiatorType(row model.Dispute) string {
|
||||
if row.InitiatorType == disputeInitiatorAdmin {
|
||||
return disputeInitiatorAdmin
|
||||
}
|
||||
return disputeInitiatorUser
|
||||
}
|
||||
|
||||
func adminDisputeTargetUserID(order model.RentalOrder, role string) (uint64, error) {
|
||||
switch role {
|
||||
case disputeTargetOwner:
|
||||
return order.OwnerID, nil
|
||||
case disputeTargetRenter:
|
||||
return order.RenterID, nil
|
||||
default:
|
||||
return 0, ErrInvalidDispute
|
||||
}
|
||||
}
|
||||
|
||||
func appendAdminCreatedDisputeNotifications(tx *gorm.DB, order model.RentalOrder, disputeID uint64, title string, content string) error {
|
||||
if isPlatformManagedOrder(order) {
|
||||
return notification.Append(tx, notification.Entry{
|
||||
UserID: order.RenterID,
|
||||
Type: "dispute",
|
||||
Title: title,
|
||||
Content: content,
|
||||
BizType: "dispute",
|
||||
BizID: &disputeID,
|
||||
})
|
||||
}
|
||||
return notification.Append(tx,
|
||||
notification.Entry{
|
||||
UserID: order.RenterID,
|
||||
Type: "dispute",
|
||||
Title: title,
|
||||
Content: content,
|
||||
BizType: "dispute",
|
||||
BizID: &disputeID,
|
||||
},
|
||||
notification.Entry{
|
||||
UserID: order.OwnerID,
|
||||
Type: "dispute",
|
||||
Title: title,
|
||||
Content: content,
|
||||
BizType: "dispute",
|
||||
BizID: &disputeID,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user