Дашборд: переводы обоих направлений, переключатель «Сегодня / Всего»

На 9 при одном входящем переводе (Витвицкий) дашборд показывал «Переводов
ещё нет»: сводка и «Последние переводы» считали только исходящие, и только
последние 200.

- Сводка по направлениям: исходящие и входящие — всего, исполнено, в работе,
  отказ / истёк срок; исход — по состоянию «чего ждёт заявка» (livestatus.go),
  то же, что в журнале: принятый, но не исполненный перевод — «в работе».
- Период «Сегодня / Всего» (?period=, выбор помнится в куке; по умолчанию —
  сегодня, с 00:00 МСК). Плитка «Переводы» ведёт в журнал, счётчики за всё время.
- «Последние переводы» — общая лента журнала (без анкет участника): направление,
  инвестор, контрагент, статус и строка состояния.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn
This commit is contained in:
fontvielle
2026-10-23 15:19:13 +03:00
parent a0ffbfa721
commit ffd69b0698
4 changed files with 202 additions and 59 deletions
+76
View File
@@ -0,0 +1,76 @@
package lkgateway
import (
"context"
"net/http/httptest"
"strings"
"testing"
"time"
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/m2m"
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/m2mcore"
)
// Дашборд: исходящие и входящие, за сегодня и за всё время (23.10.2026: считались только исходящие —
// на 9 при одном входящем было «Переводов ещё нет»).
func TestHomeSummary(t *testing.T) {
ctx := context.Background()
repo := m2mcore.NewMemoryRepository()
svc := NewService(Config{Repository: repo, Sender: &flakySender{}})
now := time.Now()
mk := func(guid string, st m2mcore.State, at time.Time) {
req := sendFailRequest()
req.Header.GUID = m2m.UUID(guid)
if _, err := repo.Create(ctx, &m2mcore.Deal{ID: guid, GUID: m2m.UUID(guid), State: st, Request: req, CreatedAt: at, UpdatedAt: at}); err != nil {
t.Fatal(err)
}
}
mk("home-done", m2mcore.StateDone, now.Add(-48*time.Hour))
mk("home-rej", m2mcore.StateRejected, now)
mk("home-wait", m2mcore.StateAwaitingDecision, now)
saved := globalIncomingStore
globalIncomingStore = &IncomingStore{items: map[string]*IncomingTransfer{
"in1": {ID: "in1", GUID: "home-in1", ReceivedAt: now, Status: "pending", InvestorLast: "Витвицкий", InvestorFirst: "Владислав"},
}}
defer func() { globalIncomingStore = saved }()
sum, all, recent := homeSummary(ctx, svc, "today")
if want := (homePeriod{Out: homeDir{Total: 2, InProgress: 1, Failed: 1}, In: homeDir{Total: 1, InProgress: 1}}); sum != want {
t.Fatalf("сегодня: %+v", sum)
}
if want := (homePeriod{Out: homeDir{Total: 3, Done: 1, InProgress: 1, Failed: 1}, In: homeDir{Total: 1, InProgress: 1}}); all != want {
t.Fatalf("всего: %+v", all)
}
if len(recent) != 3 {
t.Fatalf("последние за сегодня: %d", len(recent))
}
if s, _, r := homeSummary(ctx, svc, "all"); s != all || len(r) != 4 {
t.Fatalf("за всё время: %+v, строк %d", s, len(r))
}
a, err := newAdmin()
if err != nil {
t.Fatal(err)
}
rec := httptest.NewRecorder()
render(rec, a.home, homeData{page: nowPage("Дашборд", "home"), Period: "today", Sum: sum, All: all, Recent: recent})
body := rec.Body.String()
for _, want := range []string{"Переводы за сегодня", `class="on">Сегодня`, "↘ Входящие — к нам", "Витвицкий В.", "3 исходящих · 1 входящих", "весь журнал"} {
if !strings.Contains(body, want) {
t.Errorf("на дашборде нет %q", want)
}
}
w := httptest.NewRecorder()
if p := homePeriodOf(w, httptest.NewRequest("GET", "/admin/?period=all", nil)); p != "all" || !strings.Contains(w.Header().Get("Set-Cookie"), "bj_home_period=all") {
t.Fatalf("выбор периода: %s, %q", p, w.Header().Get("Set-Cookie"))
}
r := httptest.NewRequest("GET", "/admin/", nil)
r.Header.Set("Cookie", "bj_home_period=all")
if p := homePeriodOf(httptest.NewRecorder(), r); p != "all" {
t.Fatalf("период из куки: %s", p)
}
if p := homePeriodOf(httptest.NewRecorder(), httptest.NewRequest("GET", "/admin/", nil)); p != "today" {
t.Fatalf("по умолчанию: %s", p)
}
}