180 lines
5.7 KiB
Go
180 lines
5.7 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"affiliate_dash/internal/model"
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/clause"
|
|
)
|
|
|
|
// CorrectMismatchExpectedUID 客服修改买家填写的 UID,提货号继续留在原订单上。
|
|
// 改成当前扫码角色的 UID 时解除阻断并记为已绑定;改成其他 UID 只更新期望值,仍保持阻断。
|
|
func (s *RockService) CorrectMismatchExpectedUID(ctx context.Context, tokenID uint, expectedUID string) (*model.RockToken, error) {
|
|
trimmedUID := strings.TrimSpace(expectedUID)
|
|
if trimmedUID == "" || !isDigits(trimmedUID) || len(trimmedUID) > 15 {
|
|
return nil, errors.New("请填写 15 位以内的纯数字 UID")
|
|
}
|
|
|
|
var token model.RockToken
|
|
err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&token, tokenID).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return errors.New("提货号不存在")
|
|
}
|
|
return err
|
|
}
|
|
if token.Status != model.RockStatusMismatch {
|
|
return errors.New("仅绑定错误的提货号可以修改买家 UID")
|
|
}
|
|
if strings.TrimSpace(token.AssignedOrderNo) == "" {
|
|
return errors.New("该提货号没有关联订单,不能修改 UID")
|
|
}
|
|
|
|
now := time.Now()
|
|
actualUID := strings.TrimSpace(token.ActualRoleID)
|
|
updates := map[string]interface{}{
|
|
"expected_uid": trimmedUID,
|
|
"updated_at": now,
|
|
}
|
|
if actualUID != "" && trimmedUID == actualUID {
|
|
updates["status"] = model.RockStatusBound
|
|
updates["mismatch_reason"] = ""
|
|
updates["mismatch_at"] = nil
|
|
updates["bound_at"] = now
|
|
token.Status = model.RockStatusBound
|
|
token.MismatchReason = ""
|
|
token.MismatchAt = nil
|
|
token.BoundAt = &now
|
|
} else if actualUID != "" {
|
|
updates["mismatch_reason"] = fmt.Sprintf("预期UID(%s) 与 扫码UID(%s/%s) 不一致", trimmedUID, token.ActualRoleName, actualUID)
|
|
token.MismatchReason = updates["mismatch_reason"].(string)
|
|
}
|
|
if err := tx.Model(&token).Updates(updates).Error; err != nil {
|
|
return err
|
|
}
|
|
token.ExpectedUID = trimmedUID
|
|
|
|
var order model.FulfillmentOrder
|
|
if err := tx.Where("order_no = ?", token.AssignedOrderNo).First(&order).Error; err == nil {
|
|
data := resultDataMap(order.ResultData)
|
|
data["expected_uid"] = trimmedUID
|
|
raw, marshalErr := json.Marshal(data)
|
|
if marshalErr != nil {
|
|
return marshalErr
|
|
}
|
|
if err := tx.Model(&order).Update("result_data", string(raw)).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return writeAudit(tx, nil, nil, nil, "rock_token.correct_expected_uid", "rock_token", fmt.Sprintf("%d", token.ID), map[string]interface{}{
|
|
"assigned_order_no": token.AssignedOrderNo,
|
|
"expected_uid": trimmedUID,
|
|
"actual_role_id": actualUID,
|
|
"status": token.Status,
|
|
})
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &token, nil
|
|
}
|
|
|
|
func isDigits(value string) bool {
|
|
if value == "" {
|
|
return false
|
|
}
|
|
for _, r := range value {
|
|
if r < '0' || r > '9' {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// ResetMismatchToken 客服人工重置解冻绑定错误或作废的 Token
|
|
func (s *RockService) ResetMismatchToken(ctx context.Context, tokenID uint) error {
|
|
var token model.RockToken
|
|
if err := s.db.WithContext(ctx).First(&token, tokenID).Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
if token.Status != model.RockStatusMismatch && token.Status != model.RockStatusAbnormal {
|
|
return fmt.Errorf("仅 mismatch 或 abnormal 状态的 Token 允许人工解冻重置")
|
|
}
|
|
|
|
s.refreshCache.Delete(token.UpstreamTokenID)
|
|
|
|
// 重置回 available,清空订单关联与异常标记
|
|
return s.db.WithContext(ctx).Model(&token).Updates(map[string]interface{}{
|
|
"status": model.RockStatusAvailable,
|
|
"assigned_order_no": "",
|
|
"expected_uid": "",
|
|
"actual_role_name": "",
|
|
"actual_role_id": "",
|
|
"actual_channel": "",
|
|
"initial_bound_snapshot": "",
|
|
"mismatch_at": nil,
|
|
"mismatch_reason": "",
|
|
"using_at": nil,
|
|
"updated_at": time.Now(),
|
|
}).Error
|
|
}
|
|
|
|
// UpdateTokenRemark 更新 Token 备注,并支持联动流转状态(例如上报后置为 abnormal 失效)
|
|
func (s *RockService) UpdateTokenRemark(ctx context.Context, tokenID uint, remark string, nextStatus string) (*model.RockToken, error) {
|
|
var token model.RockToken
|
|
if err := s.db.WithContext(ctx).First(&token, tokenID).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
updates := map[string]interface{}{
|
|
"remark": strings.TrimSpace(remark),
|
|
"updated_at": time.Now(),
|
|
}
|
|
|
|
trimmedStatus := strings.TrimSpace(nextStatus)
|
|
if trimmedStatus != "" {
|
|
switch trimmedStatus {
|
|
case model.RockStatusAbnormal, model.RockStatusAvailable, model.RockStatusMismatch:
|
|
updates["status"] = trimmedStatus
|
|
token.Status = trimmedStatus
|
|
if trimmedStatus == model.RockStatusAvailable {
|
|
updates["using_at"] = nil
|
|
}
|
|
if trimmedStatus == model.RockStatusAbnormal {
|
|
s.refreshCache.Delete(token.UpstreamTokenID)
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("不支持的目标状态: %s", trimmedStatus)
|
|
}
|
|
}
|
|
|
|
if err := s.db.WithContext(ctx).Model(&token).Updates(updates).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
token.Remark = updates["remark"].(string)
|
|
|
|
_ = writeAudit(s.db.WithContext(ctx), nil, nil, nil, "rock_token.update_remark", "rock_token", fmt.Sprintf("%d", token.ID), map[string]interface{}{
|
|
"remark": token.Remark,
|
|
"status": token.Status,
|
|
})
|
|
|
|
return &token, nil
|
|
}
|
|
|
|
// MarkTokenAbnormal 快捷标记为上报失效(算作废,不可再用)
|
|
func (s *RockService) MarkTokenAbnormal(ctx context.Context, tokenID uint, reason string) (*model.RockToken, error) {
|
|
remark := strings.TrimSpace(reason)
|
|
if remark == "" {
|
|
remark = "已上报上游 (失效作废)"
|
|
}
|
|
return s.UpdateTokenRemark(ctx, tokenID, remark, model.RockStatusAbnormal)
|
|
}
|