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) }