feat(csmanualentry): 手动录单支持收号出号分组
- 录单记录新增组别(收号/出号),唯一约束放宽为编号+组别,同一编号两组各可登记一次 - 操作人选项支持组别标记,录单选中后自动带出组别 - 列表新增组别列与筛选,今日统计按收号/出号分列
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"hfb_sys/backend/internal/database"
|
||||
"hfb_sys/backend/internal/model"
|
||||
@@ -61,36 +62,36 @@ func TestRepositoryOptionCRUDAndEntryPlatformOperator(t *testing.T) {
|
||||
}
|
||||
|
||||
// 1. 选项 CRUD
|
||||
platform, err := repo.CreateOption(ctx, OptionTypePlatform, "网易大神")
|
||||
platform, err := repo.CreateOption(ctx, OptionTypePlatform, "网易大神", "")
|
||||
if err != nil {
|
||||
t.Fatalf("创建平台选项失败: %v", err)
|
||||
}
|
||||
// 同类型第二个平台,用于验证重命名撞名(唯一键是 type+name,跨类型不算冲突)
|
||||
if _, err := repo.CreateOption(ctx, OptionTypePlatform, "交易猫"); err != nil {
|
||||
if _, err := repo.CreateOption(ctx, OptionTypePlatform, "交易猫", ""); err != nil {
|
||||
t.Fatalf("创建第二个平台选项失败: %v", err)
|
||||
}
|
||||
operator, err := repo.CreateOption(ctx, OptionTypeOperator, "技术测试")
|
||||
operator, err := repo.CreateOption(ctx, OptionTypeOperator, "技术测试", "")
|
||||
if err != nil {
|
||||
t.Fatalf("创建操作人选项失败: %v", err)
|
||||
}
|
||||
if _, err := repo.CreateOption(ctx, OptionTypePlatform, "网易大神"); !errors.Is(err, ErrOptionExists) {
|
||||
if _, err := repo.CreateOption(ctx, OptionTypePlatform, "网易大神", ""); !errors.Is(err, ErrOptionExists) {
|
||||
t.Fatalf("重复平台应返回 ErrOptionExists, got %v", err)
|
||||
}
|
||||
if _, err := repo.CreateOption(ctx, "unknown", "x"); !errors.Is(err, ErrOptionTypeInvalid) {
|
||||
if _, err := repo.CreateOption(ctx, "unknown", "x", ""); !errors.Is(err, ErrOptionTypeInvalid) {
|
||||
t.Fatalf("非法类型应返回 ErrOptionTypeInvalid, got %v", err)
|
||||
}
|
||||
// 与平台同名的操作人是允许的(不同 type)
|
||||
if _, err := repo.CreateOption(ctx, OptionTypeOperator, "网易大神"); err != nil {
|
||||
if _, err := repo.CreateOption(ctx, OptionTypeOperator, "网易大神", ""); err != nil {
|
||||
t.Fatalf("跨类型同名应允许: %v", err)
|
||||
}
|
||||
|
||||
if _, err := repo.UpdateOption(ctx, platform.ID, "网易"); err != nil {
|
||||
if _, err := repo.UpdateOption(ctx, platform.ID, "网易", ""); err != nil {
|
||||
t.Fatalf("重命名选项失败: %v", err)
|
||||
}
|
||||
if _, err := repo.UpdateOption(ctx, platform.ID, "交易猫"); !errors.Is(err, ErrOptionExists) {
|
||||
if _, err := repo.UpdateOption(ctx, platform.ID, "交易猫", ""); !errors.Is(err, ErrOptionExists) {
|
||||
t.Fatalf("重命名撞同类型已有名应返回 ErrOptionExists, got %v", err)
|
||||
}
|
||||
if _, err := repo.UpdateOption(ctx, 999999, "任何"); !errors.Is(err, ErrOptionNotFound) {
|
||||
if _, err := repo.UpdateOption(ctx, 999999, "任何", ""); !errors.Is(err, ErrOptionNotFound) {
|
||||
t.Fatalf("重命名不存在选项应返回 ErrOptionNotFound, got %v", err)
|
||||
}
|
||||
|
||||
@@ -106,7 +107,7 @@ func TestRepositoryOptionCRUDAndEntryPlatformOperator(t *testing.T) {
|
||||
}
|
||||
|
||||
// 2. 录单写入平台 / 操作人
|
||||
entry, err := repo.Create(ctx, admin.ID, "客服小张", "cs001", "202610130001", "网易", "技术测试", "备注1")
|
||||
entry, err := repo.Create(ctx, admin.ID, "客服小张", "cs001", "202610130001", EntryGroupCollect, "网易", "技术测试", "备注1")
|
||||
if err != nil {
|
||||
t.Fatalf("录单失败: %v", err)
|
||||
}
|
||||
@@ -115,7 +116,7 @@ func TestRepositoryOptionCRUDAndEntryPlatformOperator(t *testing.T) {
|
||||
}
|
||||
|
||||
// 修正录单的平台与操作人
|
||||
updated, err := repo.Update(ctx, entry.ID, "202610130001", "网易", "客服小李", "备注2")
|
||||
updated, err := repo.Update(ctx, entry.ID, "202610130001", EntryGroupCollect, "网易", "客服小李", "备注2")
|
||||
if err != nil {
|
||||
t.Fatalf("修正录单失败: %v", err)
|
||||
}
|
||||
@@ -154,3 +155,78 @@ func TestRepositoryOptionCRUDAndEntryPlatformOperator(t *testing.T) {
|
||||
t.Fatalf("删除选项后历史录单操作人 = %q, want 客服小李", after.Operator)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepositoryEntryGroup(t *testing.T) {
|
||||
repo, db := newManualEntryTestRepo(t)
|
||||
ctx := t.Context()
|
||||
|
||||
admin := model.AdminUser{Username: "cs002", Nickname: "客服小李", PasswordHash: "x"}
|
||||
if err := db.Create(&admin).Error; err != nil {
|
||||
t.Fatalf("创建管理员失败: %v", err)
|
||||
}
|
||||
|
||||
// 同一编号:收号组登记一次
|
||||
collect, err := repo.Create(ctx, admin.ID, "客服小李", "cs002", "202611050473/0", EntryGroupCollect, "", "", "")
|
||||
if err != nil {
|
||||
t.Fatalf("收号组录单失败: %v", err)
|
||||
}
|
||||
if collect.EntryGroup != EntryGroupCollect {
|
||||
t.Fatalf("收号组别 = %q, want collect", collect.EntryGroup)
|
||||
}
|
||||
|
||||
// 同组重复登记应冲突
|
||||
if _, err := repo.Create(ctx, admin.ID, "客服小李", "cs002", "202611050473/0", EntryGroupCollect, "", "", ""); !errors.Is(err, ErrEntryNoExists) {
|
||||
t.Fatalf("同组重复编号应返回 ErrEntryNoExists, got %v", err)
|
||||
}
|
||||
|
||||
// 出号组登记同一编号应成功(编号绑定两个客服/两个组)
|
||||
sell, err := repo.Create(ctx, admin.ID, "客服小李", "cs002", "202611050473/0", EntryGroupSell, "", "", "")
|
||||
if err != nil {
|
||||
t.Fatalf("出号组录单失败: %v", err)
|
||||
}
|
||||
if sell.EntryGroup != EntryGroupSell {
|
||||
t.Fatalf("出号组别 = %q, want sell", sell.EntryGroup)
|
||||
}
|
||||
|
||||
// 按组别筛选
|
||||
list, err := repo.List(ctx, ListQuery{EntryNo: "202611050473/0", EntryGroup: EntryGroupSell, Page: 1, PageSize: 20})
|
||||
if err != nil {
|
||||
t.Fatalf("按组别查询失败: %v", err)
|
||||
}
|
||||
if list.Total != 1 || list.Items[0].EntryGroup != EntryGroupSell {
|
||||
t.Fatalf("按出号组查询 = %+v, want 1 条 sell 记录", list.Items)
|
||||
}
|
||||
|
||||
// 今日统计按组分列
|
||||
stats, err := repo.TodayAgentStats(ctx, time.Now())
|
||||
if err != nil {
|
||||
t.Fatalf("今日统计失败: %v", err)
|
||||
}
|
||||
if len(stats) != 1 {
|
||||
t.Fatalf("今日统计客服数 = %d, want 1", len(stats))
|
||||
}
|
||||
if stats[0].Count != 2 || stats[0].CollectCount != 1 || stats[0].SellCount != 1 {
|
||||
t.Fatalf("今日统计 = %+v, want 总量2 收号1 出号1", stats[0])
|
||||
}
|
||||
|
||||
// 修正组别:出号改成收号后与原收号记录冲突
|
||||
if _, err := repo.Update(ctx, sell.ID, "202611050473/0", EntryGroupCollect, "", "", ""); !errors.Is(err, ErrEntryNoExists) {
|
||||
t.Fatalf("改成冲突组别应返回 ErrEntryNoExists, got %v", err)
|
||||
}
|
||||
|
||||
// 操作人选项组别标记
|
||||
operator, err := repo.CreateOption(ctx, OptionTypeOperator, "测试操作", EntryGroupCollect)
|
||||
if err != nil {
|
||||
t.Fatalf("创建操作人选项失败: %v", err)
|
||||
}
|
||||
if operator.GroupTag != EntryGroupCollect {
|
||||
t.Fatalf("操作人组别标记 = %q, want collect", operator.GroupTag)
|
||||
}
|
||||
updated, err := repo.UpdateOption(ctx, operator.ID, "测试操作", EntryGroupSell)
|
||||
if err != nil {
|
||||
t.Fatalf("更新操作人组别失败: %v", err)
|
||||
}
|
||||
if updated.GroupTag != EntryGroupSell {
|
||||
t.Fatalf("更新后组别标记 = %q, want sell", updated.GroupTag)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user