diff --git a/internal/lkgateway/lkapi.go b/internal/lkgateway/lkapi.go index 3370c3d..02eb77b 100644 --- a/internal/lkgateway/lkapi.go +++ b/internal/lkgateway/lkapi.go @@ -299,7 +299,8 @@ func registerLKAPI(mux *http.ServeMux, rc *RuntimeConfig, svc *Service) { if err == nil && len(pf.Positions) == 0 && len(pf.Cash) == 0 { // В торговой системе клиента нет (не торгует через QUIK) — текущий портфель из учёта. if ar, e := lkArchivePortfolio(r.Context(), fancy, u.Contract, time.Now()); e == nil { - ar.Note = "по данным брокерского учёта на сегодня" + ar.Note = strings.TrimSpace(strings.TrimPrefix("в торговой системе позиций нет; портфель по брокерскому учёту на "+ar.AsOf+"; "+ar.Note, "; ")) + ar.Note = strings.TrimSuffix(ar.Note, "; ") pf = ar } } diff --git a/internal/lkgateway/lkarchive.go b/internal/lkgateway/lkarchive.go index 9392382..2c1c364 100644 --- a/internal/lkgateway/lkarchive.go +++ b/internal/lkgateway/lkarchive.go @@ -21,6 +21,7 @@ import ( "fmt" "sort" "strings" + "sync" "time" ) @@ -52,6 +53,13 @@ func lkArchivePortfolio(ctx context.Context, s FancyMSSQLSettings, contract stri cctx, cancel := context.WithTimeout(ctx, 40*time.Second) defer cancel() day := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, time.UTC) + // Мы читаем копию учёта; она может отставать. Позже её последнего дня данных нет — + // показываем портфель на последний день копии и говорим об этом. + note := "" + if last := spectreAsOf(cctx, conn); !last.IsZero() && day.After(last) { + day = last + note = "данные брокерского учёта доступны по " + last.Format("02.01.2006") + } today := time.Now() isPast := day.Before(time.Date(today.Year(), today.Month(), today.Day(), 0, 0, 0, 0, time.UTC)) @@ -110,7 +118,7 @@ func lkArchivePortfolio(ctx context.Context, s FancyMSSQLSettings, contract stri } } - out := &LKPortfolio{Source: "spectre", AsOf: day.Format("02.01.2006")} + out := &LKPortfolio{Source: "spectre", AsOf: day.Format("02.01.2006"), Note: note} fx := spectreFx(cctx, conn, day) byISIN := map[string]*LKPosition{} cash := map[string]float64{} @@ -182,11 +190,34 @@ func lkArchivePortfolio(ctx context.Context, s FancyMSSQLSettings, contract stri } lkPortfolioTotals(out) if c.Closed { - out.Note = "договор закрыт" + out.Note = strings.TrimSpace(strings.TrimPrefix(out.Note+"; договор закрыт", "; ")) } return out, nil } +// spectreAsOf — последний день, за который в копии учёта есть документы (кэш 10 минут). +var spectreAsOfCache struct { + mu sync.Mutex + at time.Time + v time.Time +} + +func spectreAsOf(ctx context.Context, conn *sql.DB) time.Time { + spectreAsOfCache.mu.Lock() + defer spectreAsOfCache.mu.Unlock() + if time.Since(spectreAsOfCache.at) < 10*time.Minute { + return spectreAsOfCache.v + } + var d sql.NullTime + _ = conn.QueryRowContext(ctx, `SELECT MAX(R_DATE) FROM OD_DOCS WHERE YEAR(R_DATE) < 9000`).Scan(&d) + v := time.Time{} + if d.Valid { + v = time.Date(d.Time.Year(), d.Time.Month(), d.Time.Day(), 0, 0, 0, 0, time.UTC) + } + spectreAsOfCache.at, spectreAsOfCache.v = time.Now(), v + return v +} + func abs(v float64) float64 { if v < 0 { return -v diff --git a/modules/lk/lk_test.go b/modules/lk/lk_test.go index 1e67c8c..6f8f3c8 100644 --- a/modules/lk/lk_test.go +++ b/modules/lk/lk_test.go @@ -343,7 +343,7 @@ func TestBoardPages(t *testing.T) { } } card := get("/security?isin=RU0009029540") - for _, want := range []string{"ПАО Сбербанк", "Код CFI", "275,67", "дивиденды", "Перевести эту бумагу"} { + for _, want := range []string{"ПАО Сбербанк", "275,67", "дивиденды", "Перевести эту бумагу", "← Портфель"} { if !strings.Contains(card, want) { t.Errorf("в карточке бумаги нет %q", want) } diff --git a/modules/lk/templates.go b/modules/lk/templates.go index f601e52..0a991cc 100644 --- a/modules/lk/templates.go +++ b/modules/lk/templates.go @@ -440,9 +440,9 @@ input[type=checkbox]{width:20px;min-height:20px;height:20px} {{$s := .S}}
- ← Реестр + ← Портфель
{{if $s.Name}}{{$s.Name}}{{else}}{{.ISIN}}{{end}}
-
{{.ISIN}}{{with $s.Kind}} · {{.}}{{end}}{{with $s.RegNumber}} · {{.}}{{end}}
+
{{.ISIN}}{{with $s.Kind}} · {{.}}{{end}}
{{with $s.Position}}В портфеле{{fmtnum .Quantity}} @@ -462,10 +462,7 @@ input[type=checkbox]{width:20px;min-height:20px;height:20px} {{with $s.Kind}}
Вид бумаги{{.}}
{{end}} {{if gt $s.Face 0.0}}
Номинал{{fmtmoney $s.Face}} ₽
{{end}} {{with $s.Maturity}}
Погашение{{.}}
{{end}} - {{with $s.RegNumber}}
Государственная регистрация{{.}}
{{end}} - {{with $s.CFI}}
Код CFI{{.}}
{{end}} -
Обслуживание в депозитарии{{if $s.Accepted}}принята{{else}}не принята{{end}}
-
Для квалифицированных инвесторов{{if $s.QualOnly}}да{{else}}ограничений нет{{end}}
+ {{if $s.QualOnly}}
Доступтолько для квалифицированных инвесторов
{{end}} {{if gt $s.Price 0.0}}
Цена{{with $s.Board}} · {{.}}{{end}}{{fmtmoney $s.Price}}{{with $s.PriceIn}} {{.}}{{end}}
{{end}}
{{with $s.Position}}{{if and .Transferable (gt .Available 0.0)}}