package api import ( "context" "strings" "testing" "time" "dana/internal/storage" ) func pbxCall(uid, typ, status, client, via, channel string, min, wait, dur int) storage.PBXCallRow { base := time.Date(2026, 9, 23, 8, 0, 0, 0, time.UTC) return storage.PBXCallRow{UID: uid, Type: typ, Status: status, Client: client, ClientKey: phoneKey(client), Via: via, ViaKey: phoneKey(via), Channel: channel, UserName: "КЦ DANA общий", StartedAt: base.Add(time.Duration(min) * time.Minute), Wait: wait, Duration: dur} } func TestBuildPBXReport(t *testing.T) { calls := []storage.PBXCallRow{ pbxCall("1", "in", "success", "79281112233", "79285440094", "2гис", 0, 5, 60), pbxCall("2", "missed", "missed", "79284445566", "79285088306", "яндекс", 1, 20, 0), pbxCall("3", "missed", "missed", "79284445566", "79285088306", "яндекс", 2, 15, 0), pbxCall("4", "missed", "missed", "79287770000", "79285088306", "яндекс", 3, 10, 0), pbxCall("5", "out", "success", "79287770000", "79280470055", "все номера", 10, 0, 40), // перезвонили pbxCall("6", "in", "success", "79289990000", "79280468877", "", 11, 3, 30), // линия без канала — не в отчёте pbxCall("7", "missed", "missed", "79283330000", "79285088306", "яндекс", 12, 10, 0), pbxCall("8", "out", "success", "79283330000", "79285010458", "", 13, 0, 25), // перезвонили с линии без канала pbxCall("9", "out", "success", "79286660000", "79285010458", "", 14, 0, 25), // исходящий линии без канала } rep := buildPBXReport(calls) if rep.Total.Incoming != 5 || rep.Total.Answered != 1 || rep.Total.Missed != 4 || rep.Total.Clients != 4 || rep.Outgoing != 1 { t.Fatalf("итого: %+v, исходящих %d", rep.Total, rep.Outgoing) } // потерян только 79284445566: остальным перезвонили, в том числе с линии без канала if len(rep.Lost) != 1 || rep.Lost[0].Client != "79284445566" || rep.Lost[0].Attempts != 2 || rep.Lost[0].Channel != "Яндекс" { t.Fatalf("потерянные: %+v", rep.Lost) } if len(rep.Channels) != 3 || rep.Channels[2].Channel != "Основной номер" || rep.Channels[2].Outgoing != 1 { t.Fatalf("каналы: %+v", rep.Channels) } if rep.Total.Outgoing != 1 || rep.Total.OutTalk != 40 { t.Fatalf("исходящие по линиям: %+v", rep.Total) } from := time.Date(2026, 9, 23, 0, 0, 0, 0, time.UTC) sr := buildPBXSeries(&rep, calls, from, from.AddDate(0, 0, 1)) sum := func(v []float64) (s float64) { for _, x := range v { s += x } return } if sr.Step != "hour" || len(sr.Labels) != 24 || sum(sr.All) != 6 || sum(sr.Incoming) != 5 || sum(sr.Outgoing) != 1 || sum(sr.Missed) != 4 || sum(sr.Lost) != 1 || sr.Incoming[8] != 5 { t.Fatalf("ряды: %+v", sr) } if d := buildPBXSeries(&rep, calls, from.AddDate(0, 0, -9), from.AddDate(0, 0, 1)); d.Step != "day" || len(d.All) != 10 || d.All[9] != 6 { t.Fatalf("ряды по дням: %+v", d) } ya := rep.Channels[0] if ya.Channel != "Яндекс" || ya.Incoming != 4 || ya.Clients != 3 || ya.Missed != 4 || ya.MissedLost != 1 || ya.AnsweredRate != 0 { t.Fatalf("яндекс: %+v", ya) } } func TestPBXListsInChannelsReport(t *testing.T) { calls := []storage.PBXCallRow{ pbxCall("1", "in", "success", "89281112233", "79285440094", "2гис", 0, 5, 60), pbxCall("2", "missed", "missed", "79284445566", "79285088306", "яндекс", 1, 20, 0), pbxCall("3", "out", "success", "79287770000", "79280470055", "все номера", 2, 0, 40), pbxCall("4", "in", "success", "79289990000", "79280468877", "", 3, 3, 30), // линия без канала — не в отчёте pbxCall("5", "in", "success", "79281230000", "79280470055", "все номера", 4, 3, 30), } lists, unmapped := pbxChannelNumbers(calls) if len(lists["все номера"]) != 3 || len(lists["2гис"]) != 1 || len(lists["яндекс"]) != 1 || len(unmapped) != 1 || len(lists) != 3 { t.Fatalf("списки: %v, без канала: %v", lists, unmapped) } // книга только с выгрузкой 1С — каналы целиком из АТС book := xlsxBook(t, map[string][][]string{ "Лист_1": {{"Пациент", "Мобильный телефон", "Количество", "СуммаИтого"}, {"Иванова Анна", "79281112233", "1", "1500"}, {"Петров Олег", "79284445566", "1", "2000"}, {"Итого", "", "2", "3500"}}, }, []string{"Лист_1"}) o := matchOpts{pbxLists: lists, pbxInfo: &pbxSourceInfo{From: "2026-09-23", To: "2026-09-23", Calls: 4, Unmapped: unmapped}} res, sumAny, err := (&API{}).phoneMatchOpts(context.Background(), &progressTracker{}, "1c.xlsx", book, o) if err != nil { t.Fatal(err) } s := sumAny.(channelsSummary) if g := channelByName(s, "2Гис"); g.Leads != 1 || g.Patients != 1 || g.Revenue != 1500 { t.Errorf("2ГИС: %+v", g) } if y := channelByName(s, "Яндекс"); y.Leads != 1 || y.Patients != 1 || y.Revenue != 2000 { t.Errorf("Яндекс: %+v", y) } if c := channelByName(s, "Колл-центр (все номера)"); c.Leads != 3 || c.Patients != 2 { t.Errorf("колл-центр: %+v", c) } if s.PBX == nil || s.PBX.Calls != 4 { t.Errorf("нет сведений об АТС в итогах") } if want := "Номера каналов из АТС МегаФон"; !strings.Contains(string(res), want) { t.Errorf("в CSV нет строки %q", want) } } func TestMergePBXListsSameChannel(t *testing.T) { sheets := []Sheet{{Name: "Инстаграмм", Rows: [][]string{{"79281112233"}}}} out := mergePBXLists(sheets, map[string][]string{"инстаграм": {"79284445566"}, "яндекс": {"79287770000"}}) if len(out) != 2 || len(out[0].Rows) != 2 || out[1].Name != "яндекс" { t.Fatalf("листы: %+v", out) } }