Кабинет: архив не старше копии учёта (с пометкой), карточка бумаги без депозитарных реквизитов
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q4YVo4UMopqT5DzicqpPjL
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -440,9 +440,9 @@ input[type=checkbox]{width:20px;min-height:20px;height:20px}
|
||||
{{$s := .S}}
|
||||
<section class="board"><div class="board-row">
|
||||
<div>
|
||||
<a href="/portfolio" class="lbl" style="color:var(--dim);text-decoration:none">← Реестр</a>
|
||||
<a href="/" class="lbl" style="color:var(--dim);text-decoration:none">← Портфель</a>
|
||||
<div style="font-size:23px;font-weight:700;margin-top:8px">{{if $s.Name}}{{$s.Name}}{{else}}{{.ISIN}}{{end}}</div>
|
||||
<div class="n" style="font-size:13px;color:var(--dim);margin-top:6px">{{.ISIN}}{{with $s.Kind}} · {{.}}{{end}}{{with $s.RegNumber}} · {{.}}{{end}}</div>
|
||||
<div class="n" style="font-size:13px;color:var(--dim);margin-top:6px">{{.ISIN}}{{with $s.Kind}} · {{.}}{{end}}</div>
|
||||
</div>
|
||||
<div class="stats">
|
||||
{{with $s.Position}}<span class="stat"><span class="lbl" style="color:var(--dim)">В портфеле</span><span class="v">{{fmtnum .Quantity}}</span></span>
|
||||
@@ -462,10 +462,7 @@ input[type=checkbox]{width:20px;min-height:20px;height:20px}
|
||||
{{with $s.Kind}}<div class="row"><span>Вид бумаги</span><span>{{.}}</span></div>{{end}}
|
||||
{{if gt $s.Face 0.0}}<div class="row"><span>Номинал</span><span class="n">{{fmtmoney $s.Face}} ₽</span></div>{{end}}
|
||||
{{with $s.Maturity}}<div class="row"><span>Погашение</span><span class="n">{{.}}</span></div>{{end}}
|
||||
{{with $s.RegNumber}}<div class="row"><span>Государственная регистрация</span><span class="n">{{.}}</span></div>{{end}}
|
||||
{{with $s.CFI}}<div class="row"><span>Код CFI</span><span class="n">{{.}}</span></div>{{end}}
|
||||
<div class="row"><span>Обслуживание в депозитарии</span><span>{{if $s.Accepted}}принята{{else}}не принята{{end}}</span></div>
|
||||
<div class="row"><span>Для квалифицированных инвесторов</span><span>{{if $s.QualOnly}}да{{else}}ограничений нет{{end}}</span></div>
|
||||
{{if $s.QualOnly}}<div class="row"><span>Доступ</span><span>только для квалифицированных инвесторов</span></div>{{end}}
|
||||
{{if gt $s.Price 0.0}}<div class="row"><span>Цена{{with $s.Board}} · {{.}}{{end}}</span><span class="n">{{fmtmoney $s.Price}}{{with $s.PriceIn}} <span style="color:var(--mut)">{{.}}</span>{{end}}</span></div>{{end}}
|
||||
</div>
|
||||
{{with $s.Position}}{{if and .Transferable (gt .Available 0.0)}}
|
||||
|
||||
Reference in New Issue
Block a user