Кабинет клиента: портфель как у брокера — «Онлайн» по QUIK, «Архив» на дату и операции по SPECTRE
- lkclient.go: договор → код QUIK (БР-1545 → BR1545, +OUT), договор → D_B_CONTRACTS SPECTRE - lkonline.go: позиции DepoLimits (T2, свежая строка на счёт), средняя цена покупки, деньги MoneyLimits по валютам, цены и P&L; стоимость, доли - lkarchive.go: портфель на дату по остаткам OD_RESTS (REG_3 = договор, раздел 39, счета 50/51/52/55 и 58.01/58.02; 58.05 НКД не показываем) с откатом по OD_TURNS после даты (индекс REST+WIRDATE, знак TYPE_ проверен: сумма оборотов = остаток); оценка по котировке OD_VALUES_RATES ≤ 90 дней (в рублях за бумагу), иначе балансовая; операции из OD_WIRING (покупка/продажа/ввод/вывод/погашение/конвертация, продажа — по счёту ВЫРУЧКА) и пополнения/выводы денег; рецепт — InvestControl Hub (build_portfolio_base, deals_report) - API: /api/lk/portfolio2?view=online|archive&date=, /api/lk/ops (сделки + выплаты дохода); без клиента в QUIK онлайн падает на брокерский учёт за сегодня - кабинет: разделы Портфель (вкладки Онлайн/Архив с датой), Операции, Поручения, Документы, Профиль; депозитарные реестр и доходы из клиентского вида убраны (/portfolio → /, /income → /operations); проверено на БР-704 и БР-1545 - настройка Fancy.SpectreDatabase (по умолчанию BJ_SPECTRE_DATA) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q4YVo4UMopqT5DzicqpPjL
This commit is contained in:
+173
-126
@@ -71,33 +71,7 @@ func New(cfg Config) (*Server, error) {
|
||||
}
|
||||
return strconv.FormatFloat(f, 'f', -1, 64)
|
||||
},
|
||||
"fmtmoney": func(f float64) string { // 1 234 567,89 — с разделителем разрядов
|
||||
neg := f < 0
|
||||
if neg {
|
||||
f = -f
|
||||
}
|
||||
whole := int64(f)
|
||||
cents := int64((f-float64(whole))*100 + 0.5)
|
||||
if cents >= 100 {
|
||||
whole, cents = whole+1, 0
|
||||
}
|
||||
d := strconv.FormatInt(whole, 10)
|
||||
var b strings.Builder
|
||||
for i, c := range d {
|
||||
if i > 0 && (len(d)-i)%3 == 0 {
|
||||
b.WriteByte(' ')
|
||||
}
|
||||
b.WriteRune(c)
|
||||
}
|
||||
out := b.String()
|
||||
if cents > 0 {
|
||||
out += "," + fmt.Sprintf("%02d", cents)
|
||||
}
|
||||
if neg {
|
||||
out = "−" + out
|
||||
}
|
||||
return out
|
||||
},
|
||||
"fmtmoney": fmtMoney,
|
||||
"fmtpct": func(f float64) string {
|
||||
s := strconv.FormatFloat(f, 'f', 2, 64)
|
||||
if f > 0 {
|
||||
@@ -105,6 +79,43 @@ func New(cfg Config) (*Server, error) {
|
||||
}
|
||||
return strings.ReplaceAll(s, ".", ",") + " %"
|
||||
},
|
||||
"fmtpct1": func(f float64) string { // 84,1%
|
||||
return strings.ReplaceAll(strconv.FormatFloat(f, 'f', 1, 64), ".", ",") + "%"
|
||||
},
|
||||
"fmtsigned": func(f float64) string { // +1 234,56 / −1 234,56
|
||||
if f > 0 {
|
||||
return "+" + fmtMoney(f)
|
||||
}
|
||||
return fmtMoney(f)
|
||||
},
|
||||
"fmtprice": func(f float64) string { // цена: до 4 знаков, без хвостовых нулей, с пробелами разрядов
|
||||
if f == 0 {
|
||||
return "—"
|
||||
}
|
||||
s := strconv.FormatFloat(f, 'f', 4, 64)
|
||||
s = strings.TrimRight(strings.TrimRight(s, "0"), ".")
|
||||
whole, frac := s, ""
|
||||
if i := strings.IndexByte(s, '.'); i >= 0 {
|
||||
whole, frac = s[:i], s[i+1:]
|
||||
}
|
||||
neg := strings.HasPrefix(whole, "-")
|
||||
whole = strings.TrimPrefix(whole, "-")
|
||||
var b strings.Builder
|
||||
for i, c := range whole {
|
||||
if i > 0 && (len(whole)-i)%3 == 0 {
|
||||
b.WriteByte(' ')
|
||||
}
|
||||
b.WriteRune(c)
|
||||
}
|
||||
out := b.String()
|
||||
if frac != "" {
|
||||
out += "," + frac
|
||||
}
|
||||
if neg {
|
||||
out = "−" + out
|
||||
}
|
||||
return out
|
||||
},
|
||||
"shortid": func(s string) string {
|
||||
if len(s) > 8 {
|
||||
return s[:8] + "…"
|
||||
@@ -368,6 +379,60 @@ type portfolioResp struct {
|
||||
}
|
||||
|
||||
// operationRow — движение по счёту депо.
|
||||
// lkPortfolio — портфель как у брокера (ответ /api/lk/portfolio2).
|
||||
type lkPortfolio struct {
|
||||
Source string `json:"source"`
|
||||
AsOf string `json:"as_of"`
|
||||
Total float64 `json:"total"`
|
||||
Securities float64 `json:"securities_value"`
|
||||
CashRUB float64 `json:"cash_rub"`
|
||||
Cost float64 `json:"cost"`
|
||||
PnL float64 `json:"pnl"`
|
||||
PnLPct float64 `json:"pnl_pct"`
|
||||
Priced int `json:"priced"`
|
||||
Cash []lkCash `json:"cash"`
|
||||
Positions []lkPosition `json:"positions"`
|
||||
Note string `json:"note"`
|
||||
}
|
||||
|
||||
type lkCash struct {
|
||||
Currency string `json:"currency"`
|
||||
Amount float64 `json:"amount"`
|
||||
}
|
||||
|
||||
type lkPosition struct {
|
||||
ISIN string `json:"isin"`
|
||||
Code string `json:"code"`
|
||||
Name string `json:"name"`
|
||||
Kind string `json:"kind"`
|
||||
Quantity float64 `json:"quantity"`
|
||||
AvgPrice float64 `json:"avg_price"`
|
||||
Price float64 `json:"price"`
|
||||
InPct bool `json:"in_pct"`
|
||||
Value float64 `json:"value"`
|
||||
Cost float64 `json:"cost"`
|
||||
PnL float64 `json:"pnl"`
|
||||
PnLPct float64 `json:"pnl_pct"`
|
||||
DayPct float64 `json:"day_pct"`
|
||||
Share float64 `json:"share"`
|
||||
Currency string `json:"currency"`
|
||||
Note string `json:"note"`
|
||||
}
|
||||
|
||||
// lkOpRow — операция по брокерскому счёту (ответ /api/lk/ops).
|
||||
type lkOpRow struct {
|
||||
Date string `json:"date"`
|
||||
Kind string `json:"kind"`
|
||||
Side string `json:"side"`
|
||||
Name string `json:"name"`
|
||||
ISIN string `json:"isin"`
|
||||
Quantity float64 `json:"quantity"`
|
||||
Price float64 `json:"price"`
|
||||
Amount float64 `json:"amount"`
|
||||
Currency string `json:"currency"`
|
||||
AmountRUB float64 `json:"amount_rub"`
|
||||
}
|
||||
|
||||
type operationRow struct {
|
||||
Date string `json:"date"`
|
||||
DoneAt string `json:"done_at"`
|
||||
@@ -622,60 +687,42 @@ func (s *Server) buildHandler() http.Handler {
|
||||
return
|
||||
}
|
||||
}
|
||||
pf, err := s.portfolio(r, sess)
|
||||
if err != nil {
|
||||
s.render(w, r, "board", map[string]any{"Nav": "b", "Error": err.Error()})
|
||||
view := "online"
|
||||
q := url.Values{}
|
||||
today := time.Now().Format("2006-01-02")
|
||||
date := r.URL.Query().Get("date")
|
||||
if r.URL.Query().Get("view") == "archive" {
|
||||
view = "archive"
|
||||
if _, err := time.Parse("2006-01-02", date); err != nil || date > today {
|
||||
date = today
|
||||
}
|
||||
q.Set("view", "archive")
|
||||
q.Set("date", date)
|
||||
}
|
||||
var out struct {
|
||||
Portfolio lkPortfolio `json:"portfolio"`
|
||||
Contract string `json:"contract"`
|
||||
FullName string `json:"full_name"`
|
||||
}
|
||||
path := "/api/lk/portfolio2"
|
||||
if len(q) > 0 {
|
||||
path += "?" + q.Encode()
|
||||
}
|
||||
data := map[string]any{"Nav": "b", "View": view, "Date": date, "Today": today}
|
||||
if err := s.bj().call(reqCtx(r), http.MethodGet, path, sess, nil, &out); err != nil {
|
||||
data["Error"] = err.Error()
|
||||
data["P"] = lkPortfolio{}
|
||||
s.render(w, r, "portfolio", data)
|
||||
return
|
||||
}
|
||||
total, free, blocked, _ := portfolioView(pf.Positions)
|
||||
var cl struct {
|
||||
Claims []claimRow `json:"claims"`
|
||||
}
|
||||
_ = s.bj().call(reqCtx(r), http.MethodGet, "/api/lk/claims", sess, nil, &cl)
|
||||
active, _ := claimsView(cl.Claims)
|
||||
pos := pf.Positions
|
||||
if len(pos) > 5 {
|
||||
pos = pos[:5]
|
||||
}
|
||||
claims := cl.Claims
|
||||
if len(claims) > 5 {
|
||||
claims = claims[:5]
|
||||
}
|
||||
s.render(w, r, "board", map[string]any{
|
||||
"Nav": "b", "Contract": pf.Contract, "FullName": pf.FullName,
|
||||
"Positions": pos, "Claims": claims,
|
||||
"Total": total, "Free": free, "Blocked": blocked, "Active": active,
|
||||
"Value": pf.TotalValue, "Priced": pf.Priced,
|
||||
})
|
||||
data["P"], data["Contract"], data["FullName"] = out.Portfolio, out.Contract, out.FullName
|
||||
s.render(w, r, "portfolio", data)
|
||||
})
|
||||
|
||||
// Реестр бумаг
|
||||
mux.HandleFunc("/portfolio", func(w http.ResponseWriter, r *http.Request) {
|
||||
sess := sessionW(w, r)
|
||||
if sess == "" {
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
pf, err := s.portfolio(r, sess)
|
||||
if err != nil {
|
||||
s.render(w, r, "portfolio", map[string]any{"Nav": "p", "Error": err.Error()})
|
||||
return
|
||||
}
|
||||
total, free, blocked, _ := portfolioView(pf.Positions)
|
||||
updated := ""
|
||||
if len(pf.Positions) > 0 {
|
||||
updated = pf.Positions[0].UpdatedAt
|
||||
}
|
||||
s.render(w, r, "portfolio", map[string]any{
|
||||
"Nav": "p", "Contract": pf.Contract, "FullName": pf.FullName,
|
||||
"Positions": pf.Positions,
|
||||
"Source": pf.Source, "ReadOnly": pf.ReadOnly, "UpdatedAt": updated,
|
||||
"Total": total, "Free": free, "Blocked": blocked,
|
||||
"Value": pf.TotalValue, "Priced": pf.Priced,
|
||||
})
|
||||
http.Redirect(w, r, "/", http.StatusMovedPermanently)
|
||||
})
|
||||
|
||||
// Карточка бумаги
|
||||
mux.HandleFunc("/security", func(w http.ResponseWriter, r *http.Request) {
|
||||
sess := sessionW(w, r)
|
||||
if sess == "" {
|
||||
@@ -705,70 +752,41 @@ func (s *Server) buildHandler() http.Handler {
|
||||
return
|
||||
}
|
||||
from, to := r.URL.Query().Get("from"), r.URL.Query().Get("to")
|
||||
if from == "" {
|
||||
from = time.Now().AddDate(-1, 0, 0).Format("2006-01-02")
|
||||
}
|
||||
if to == "" {
|
||||
to = time.Now().Format("2006-01-02")
|
||||
}
|
||||
var out struct {
|
||||
Operations []operationRow `json:"operations"`
|
||||
Rows []lkOpRow `json:"operations"`
|
||||
From string `json:"from"`
|
||||
To string `json:"to"`
|
||||
Note string `json:"note"`
|
||||
}
|
||||
path := "/api/lk/operations?from=" + url.QueryEscape(from) + "&to=" + url.QueryEscape(to)
|
||||
path := "/api/lk/ops?from=" + url.QueryEscape(from) + "&to=" + url.QueryEscape(to)
|
||||
data := map[string]any{"Nav": "o", "From": from, "To": to}
|
||||
if err := s.bj().call(reqCtx(r), http.MethodGet, path, sess, nil, &out); err != nil {
|
||||
s.render(w, r, "operations", map[string]any{"Nav": "o", "Error": err.Error(), "From": from, "To": to})
|
||||
data["Error"] = err.Error()
|
||||
s.render(w, r, "operations", data)
|
||||
return
|
||||
}
|
||||
var in, outq int
|
||||
for _, o := range out.Operations {
|
||||
if o.Quantity < 0 {
|
||||
outq++
|
||||
} else {
|
||||
in++
|
||||
var buy, sell, income float64
|
||||
for _, o := range out.Rows {
|
||||
switch o.Kind {
|
||||
case "покупка", "ввод бумаг", "пополнение":
|
||||
buy += o.AmountRUB
|
||||
case "продажа", "погашение", "вывод бумаг", "вывод денег":
|
||||
sell += o.AmountRUB
|
||||
default:
|
||||
if o.Side == "in" {
|
||||
income += o.AmountRUB
|
||||
}
|
||||
}
|
||||
}
|
||||
s.render(w, r, "operations", map[string]any{"Nav": "o", "Operations": out.Operations,
|
||||
"From": from, "To": to, "Total": len(out.Operations), "In": in, "Out": outq})
|
||||
data["Rows"], data["From"], data["To"], data["Note"] = out.Rows, out.From, out.To, out.Note
|
||||
data["BuyRUB"], data["SellRUB"], data["IncomeRUB"] = buy, sell, income
|
||||
s.render(w, r, "operations", data)
|
||||
})
|
||||
|
||||
// Доходы и налоги
|
||||
mux.HandleFunc("/income", func(w http.ResponseWriter, r *http.Request) {
|
||||
sess := sessionW(w, r)
|
||||
if sess == "" {
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
from, to := r.URL.Query().Get("from"), r.URL.Query().Get("to")
|
||||
if from == "" {
|
||||
from = time.Now().Format("2006") + "-01-01"
|
||||
}
|
||||
if to == "" {
|
||||
to = time.Now().Format("2006-01-02")
|
||||
}
|
||||
var out struct {
|
||||
Income []incomeRow `json:"income"`
|
||||
Accrued float64 `json:"accrued"`
|
||||
Paid float64 `json:"paid"`
|
||||
Tax float64 `json:"tax"`
|
||||
}
|
||||
path := "/api/lk/income?from=" + url.QueryEscape(from) + "&to=" + url.QueryEscape(to)
|
||||
if err := s.bj().call(reqCtx(r), http.MethodGet, path, sess, nil, &out); err != nil {
|
||||
s.render(w, r, "income", map[string]any{"Nav": "i", "Error": err.Error(), "From": from, "To": to})
|
||||
return
|
||||
}
|
||||
var ca struct {
|
||||
Actions []corpAction `json:"actions"`
|
||||
}
|
||||
_ = s.bj().call(reqCtx(r), http.MethodGet, "/api/lk/corpactions", sess, nil, &ca)
|
||||
acts := ca.Actions
|
||||
if len(acts) > 8 {
|
||||
acts = acts[:8]
|
||||
}
|
||||
s.render(w, r, "income", map[string]any{"Nav": "i", "Income": out.Income, "From": from, "To": to,
|
||||
"Accrued": out.Accrued, "Paid": out.Paid, "Tax": out.Tax, "Actions": acts})
|
||||
http.Redirect(w, r, "/operations", http.StatusMovedPermanently)
|
||||
})
|
||||
|
||||
// Документы: готовые выписки, заявки клиента и заказ нового документа
|
||||
mux.HandleFunc("/documents", func(w http.ResponseWriter, r *http.Request) {
|
||||
sess := sessionW(w, r)
|
||||
if sess == "" {
|
||||
@@ -1048,3 +1066,32 @@ func (s *Server) mode() string {
|
||||
}
|
||||
return "direct"
|
||||
}
|
||||
|
||||
// fmtMoney — 1 234 567,89 с разделителем разрядов; отрицательные — с минусом.
|
||||
func fmtMoney(f float64) string {
|
||||
neg := f < 0
|
||||
if neg {
|
||||
f = -f
|
||||
}
|
||||
whole := int64(f)
|
||||
cents := int64((f-float64(whole))*100 + 0.5)
|
||||
if cents >= 100 {
|
||||
whole, cents = whole+1, 0
|
||||
}
|
||||
d := strconv.FormatInt(whole, 10)
|
||||
var b strings.Builder
|
||||
for i, c := range d {
|
||||
if i > 0 && (len(d)-i)%3 == 0 {
|
||||
b.WriteByte(' ')
|
||||
}
|
||||
b.WriteRune(c)
|
||||
}
|
||||
out := b.String()
|
||||
if cents > 0 {
|
||||
out += "," + fmt.Sprintf("%02d", cents)
|
||||
}
|
||||
if neg {
|
||||
out = "−" + out
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
+47
-15
@@ -83,6 +83,32 @@ func fakeBJ(t *testing.T) *httptest.Server {
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"ok": true})
|
||||
}
|
||||
})
|
||||
mux.HandleFunc("/api/lk/portfolio2", func(w http.ResponseWriter, r *http.Request) {
|
||||
if !keyOK(w, r) {
|
||||
return
|
||||
}
|
||||
view := r.URL.Query().Get("view")
|
||||
asOf := "18.09.2026"
|
||||
if view == "archive" {
|
||||
asOf = "31.12.2025"
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"contract": "BR-1", "full_name": "Клиент Тестовый", "portfolio": map[string]any{
|
||||
"source": map[bool]string{true: "spectre", false: "quik"}[view == "archive"], "as_of": asOf,
|
||||
"total": 110268.0, "securities_value": 92698.28, "cash_rub": 17569.72, "cost": 80000.0, "pnl": 12698.28, "pnl_pct": 15.87, "priced": 1,
|
||||
"cash": []map[string]any{{"currency": "SUR", "amount": 17569.72}},
|
||||
"positions": []map[string]any{{"isin": "RU0009029540", "name": "Сбербанк, ао", "kind": "акция", "quantity": 336, "avg_price": 238.1, "price": 275.887, "value": 92698.28, "cost": 80000, "pnl": 12698.28, "pnl_pct": 15.87, "day_pct": 1.2, "share": 84.1}},
|
||||
}})
|
||||
})
|
||||
mux.HandleFunc("/api/lk/ops", func(w http.ResponseWriter, r *http.Request) {
|
||||
if !keyOK(w, r) {
|
||||
return
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"from": "2025-09-18", "to": "2026-09-18", "operations": []map[string]any{
|
||||
{"date": "10.07.2026", "kind": "продажа", "side": "out", "name": "НОВАТЭК 001P-03", "isin": "RU000A10AUX8", "quantity": 1700, "price": 7793.07, "amount": 13248214.78, "currency": "SUR", "amount_rub": 13248214.78},
|
||||
{"date": "06.03.2026", "kind": "покупка", "side": "in", "name": "Сбербанк, ао", "isin": "RU0009029540", "quantity": 100, "price": 238.1, "amount": 23810, "currency": "SUR", "amount_rub": 23810},
|
||||
{"date": "12.05.2026", "kind": "дивиденды", "side": "in", "name": "Сбербанк, ао", "isin": "RU0009029540", "quantity": 336, "amount": 494.6, "currency": "SUR", "amount_rub": 494.6},
|
||||
}})
|
||||
})
|
||||
mux.HandleFunc("/api/lk/admin/bootstrap", func(w http.ResponseWriter, r *http.Request) {
|
||||
if !keyOK(w, r) {
|
||||
return
|
||||
@@ -280,19 +306,31 @@ func TestBoardPages(t *testing.T) {
|
||||
return rec.Body.String()
|
||||
}
|
||||
board := get("/")
|
||||
for _, want := range []string{"Стоимость бумаг", "Новое поручение", "Сбербанк, ао", "Отправлено в НРД", "Поручений в работе"} {
|
||||
for _, want := range []string{"Стоимость портфеля", "Новое поручение", "Сбербанк, ао", "Онлайн", "Архив", "Денежные средства", "12 698,28 ₽", "15,87 %", "84,1%"} {
|
||||
if !strings.Contains(board, want) {
|
||||
t.Errorf("на табло нет %q", want)
|
||||
t.Errorf("в портфеле нет %q", want)
|
||||
if i := strings.Index(board, "Результат"); i > 0 {
|
||||
t.Logf("фрагмент: %s", board[i:min(i+400, len(board))])
|
||||
}
|
||||
}
|
||||
}
|
||||
if !strings.Contains(board, "110 268 ₽") {
|
||||
t.Errorf("на табло нет оценки портфеля")
|
||||
t.Errorf("стоимость портфеля не показана: %s", board)
|
||||
}
|
||||
reg := get("/portfolio")
|
||||
for _, want := range []string{"ВТНКЦ00000001", "иностранный выпуск", "депозитарный учёт", "перевести"} {
|
||||
if !strings.Contains(reg, want) {
|
||||
t.Errorf("в реестре нет %q", want)
|
||||
}
|
||||
arch := get("/?view=archive&date=2025-12-31")
|
||||
if !strings.Contains(arch, "на 31.12.2025") || !strings.Contains(arch, `value="2025-12-31"`) {
|
||||
t.Errorf("архив на дату не показан")
|
||||
}
|
||||
// старые адреса ведут на новые разделы
|
||||
rec := httptest.NewRecorder()
|
||||
h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/portfolio", nil))
|
||||
if rec.Code != http.StatusMovedPermanently || rec.Header().Get("Location") != "/" {
|
||||
t.Errorf("/portfolio должен вести на портфель: %d %s", rec.Code, rec.Header().Get("Location"))
|
||||
}
|
||||
rec = httptest.NewRecorder()
|
||||
h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/income", nil))
|
||||
if rec.Code != http.StatusMovedPermanently || rec.Header().Get("Location") != "/operations" {
|
||||
t.Errorf("/income должен вести на операции: %d %s", rec.Code, rec.Header().Get("Location"))
|
||||
}
|
||||
orders := get("/orders")
|
||||
if !strings.Contains(orders, "Поручения депозитарию") || !strings.Contains(orders, "Перевод в другой депозитарий") {
|
||||
@@ -311,17 +349,11 @@ func TestBoardPages(t *testing.T) {
|
||||
}
|
||||
}
|
||||
ops := get("/operations")
|
||||
for _, want := range []string{"Операции по счетам депо", "зачисление", "Ликвидность, пай", "Отчёт клиринговой организации"} {
|
||||
for _, want := range []string{"Операции", "продажа", "покупка", "дивиденды", "НОВАТЭК 001P-03", "−13 248 214,78 ₽", "+494,60 ₽", "Продажи"} {
|
||||
if !strings.Contains(ops, want) {
|
||||
t.Errorf("в операциях нет %q", want)
|
||||
}
|
||||
}
|
||||
inc := get("/income")
|
||||
for _, want := range []string{"Выплачено дохода", "494,60 ₽", "Удержан налог", "дивиденды"} {
|
||||
if !strings.Contains(inc, want) {
|
||||
t.Errorf("в доходах нет %q", want)
|
||||
}
|
||||
}
|
||||
prof := get("/profile")
|
||||
if !strings.Contains(prof, "Счета депо") || !strings.Contains(prof, "ВТНКЦ00000001") {
|
||||
t.Errorf("профиль: %s", prof)
|
||||
|
||||
+60
-133
@@ -33,6 +33,13 @@ a{color:var(--acc)}a:hover{color:#1e3395}
|
||||
.top .who{color:var(--dim);font-size:12px;white-space:nowrap}
|
||||
.top .exit{color:var(--dim);text-decoration:none}
|
||||
.lnk{background:none;border:0;padding:0;font:inherit;color:inherit;cursor:pointer;text-decoration:underline;text-underline-offset:2px}
|
||||
.tabs{display:flex;gap:6px;align-items:center;flex-wrap:wrap;margin:0 0 16px}
|
||||
.tab{font-family:var(--mono);font-size:11px;font-weight:500;letter-spacing:.1em;text-transform:uppercase;padding:8px 14px;border:1px solid var(--line-2);border-radius:3px;text-decoration:none;color:var(--ink-2)}
|
||||
.tab.on{background:var(--ink);color:#f3f0e7;border-color:var(--ink)}
|
||||
.tabform{display:flex;gap:8px;align-items:center;margin-left:8px}
|
||||
.tabform input[type=date]{padding:7px 10px;border:1px solid var(--line-2);border-radius:3px;background:#fff;font:inherit;font-size:14px}
|
||||
.pos{color:var(--ok-tx)}.neg{color:var(--err-tx)}
|
||||
.kind{font-family:var(--mono);font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;color:var(--mut)}
|
||||
.cta{display:inline-flex;align-items:center;gap:8px;min-height:38px;padding:0 14px;background:var(--amber);color:var(--ink);text-decoration:none;border:0;border-radius:3px;cursor:pointer;font:inherit}
|
||||
.cta:hover{filter:brightness(1.05);color:var(--ink)}
|
||||
.board{background:var(--board-2);color:#f3f0e7;border-bottom:3px solid var(--amber);padding:22px 20px}
|
||||
@@ -96,11 +103,9 @@ input[type=checkbox]{width:20px;min-height:20px;height:20px}
|
||||
<a class="brand" href="/" title="На табло" style="text-decoration:none;color:inherit">{{template "mark" .}}<span class="lbl" style="font-size:12px;letter-spacing:.14em">{{.Title}}</span></a>
|
||||
<nav>
|
||||
{{if .IsAdmin}}<a href="/clients" class="lbl {{if eq .Nav "a"}}on{{end}}">Клиенты</a>{{end}}
|
||||
<a href="/" class="lbl {{if eq .Nav "b"}}on{{end}}">Табло</a>
|
||||
<a href="/portfolio" class="lbl {{if eq .Nav "p"}}on{{end}}">Реестр</a>
|
||||
<a href="/orders" class="lbl {{if eq .Nav "c"}}on{{end}}">Поручения</a>
|
||||
<a href="/" class="lbl {{if eq .Nav "b"}}on{{end}}">Портфель</a>
|
||||
<a href="/operations" class="lbl {{if eq .Nav "o"}}on{{end}}">Операции</a>
|
||||
<a href="/income" class="lbl {{if eq .Nav "i"}}on{{end}}">Доходы</a>
|
||||
<a href="/orders" class="lbl {{if eq .Nav "c"}}on{{end}}">Поручения</a>
|
||||
<a href="/documents" class="lbl {{if eq .Nav "d"}}on{{end}}">Документы</a>
|
||||
<a href="/profile" class="lbl {{if eq .Nav "u"}}on{{end}}">Профиль</a>
|
||||
</nav>
|
||||
@@ -141,86 +146,51 @@ input[type=checkbox]{width:20px;min-height:20px;height:20px}
|
||||
</div>
|
||||
</body></html>{{end}}
|
||||
|
||||
{{define "board"}}{{template "head" .}}{{template "nav" .}}
|
||||
{{define "portfolio"}}{{template "head" .}}{{template "nav" .}}{{$p := .P}}
|
||||
<section class="board"><div class="board-row">
|
||||
<div>
|
||||
<span class="lbl" style="color:var(--dim)">{{if gt .Value 0.0}}Стоимость бумаг{{else}}Бумаг в реестре{{end}}</span>
|
||||
<div class="big" style="margin-top:8px">{{if gt .Value 0.0}}{{fmtmoney .Value}} ₽{{else}}{{.Total}}{{end}}</div>
|
||||
<div class="n" style="font-size:13px;color:var(--dim);margin-top:6px">{{with .FullName}}{{.}} · {{end}}договор {{.Contract}}{{if gt .Value 0.0}} · оценка по {{.Priced}} из {{.Total}} бумаг{{end}}</div>
|
||||
<span class="lbl" style="color:var(--dim)">Стоимость портфеля{{if eq .View "archive"}} на {{$p.AsOf}}{{end}}</span>
|
||||
<div class="big" style="margin-top:8px">{{fmtmoney $p.Total}} ₽</div>
|
||||
<div class="n" style="font-size:13px;color:var(--dim);margin-top:6px">{{with .FullName}}{{.}} · {{end}}договор {{.Contract}}{{if and (ne .View "archive") $p.AsOf}} · данные на {{$p.AsOf}}{{end}}{{if $p.Note}} · {{$p.Note}}{{end}}</div>
|
||||
</div>
|
||||
<div class="stats">
|
||||
<span class="stat"><span class="lbl" style="color:var(--dim)">Свободно к переводу</span><span class="v">{{.Free}}</span></span>
|
||||
<span class="stat"><span class="lbl" style="color:var(--dim)">С ограничением</span><span class="v" style="color:var(--amber)">{{.Blocked}}</span></span>
|
||||
<span class="stat"><span class="lbl" style="color:var(--dim)">Поручений в работе</span><span class="v">{{.Active}}</span></span>
|
||||
<span class="stat"><span class="lbl" style="color:var(--dim)">Бумаги</span><span class="v">{{fmtmoney $p.Securities}} ₽</span></span>
|
||||
<span class="stat"><span class="lbl" style="color:var(--dim)">Деньги</span><span class="v">{{fmtmoney $p.CashRUB}} ₽</span></span>
|
||||
<span class="stat"><span class="lbl" style="color:var(--dim)">Результат</span><span class="v" style="color:{{if lt $p.PnL 0.0}}#f0a090{{else if gt $p.PnL 0.0}}#8fd19e{{else}}inherit{{end}}">{{if ne $p.Cost 0.0}}{{fmtsigned $p.PnL}} ₽ · {{fmtpct $p.PnLPct}}{{else}}—{{end}}</span></span>
|
||||
</div>
|
||||
</div></section>
|
||||
<main>
|
||||
{{if .Flash}}<div class="msg ok">{{.Flash}}</div>{{end}}
|
||||
{{if .Error}}<div class="msg err">{{.Error}}</div>{{end}}
|
||||
<section class="sec">
|
||||
<div class="sec-h"><h1 class="lbl" style="font-size:12px;letter-spacing:.14em">Последние поручения</h1><a href="/orders" style="font-size:13px;text-decoration:none">все поручения</a></div>
|
||||
{{if .Claims}}<div class="tw"><table>
|
||||
<thead><tr><th>Номер</th><th>Дата</th><th>Вид поручения</th><th class="num">Бумаг</th><th>Состояние</th></tr></thead>
|
||||
<tbody>{{range .Claims}}<tr>
|
||||
<td class="n">{{shortid .GUID}}</td>
|
||||
<td class="n">{{.CreatedAt}}</td>
|
||||
<td>Перевод в другой депозитарий</td>
|
||||
<td class="num">{{.Securit}}</td>
|
||||
<td><span class="chip {{if eq .Status "done"}}ok{{else if eq .Status "rejected"}}err{{else if eq .Status "declined"}}err{{else if eq .Status "error"}}err{{else}}go{{end}}">{{.StatusRu}}</span>{{with .Reason}}<div class="mut">{{.}}</div>{{end}}</td>
|
||||
</tr>{{end}}</tbody></table></div>
|
||||
{{else}}<p class="empty">Поручений пока нет. Нажмите «Новое поручение», чтобы перевести бумаги в другой депозитарий.</p>{{end}}
|
||||
</section>
|
||||
<div class="tabs">
|
||||
<a class="tab {{if ne .View "archive"}}on{{end}}" href="/">Онлайн</a>
|
||||
<a class="tab {{if eq .View "archive"}}on{{end}}" href="/?view=archive">Архив</a>
|
||||
{{if eq .View "archive"}}<form method="get" action="/" class="tabform"><input type="hidden" name="view" value="archive"><input type="date" name="date" value="{{.Date}}" max="{{.Today}}"><button class="btn sm" type="submit">Показать</button></form>{{end}}
|
||||
<span class="mut" style="margin-left:auto;font-size:12.5px">{{if eq .View "archive"}}Портфель по брокерскому учёту на выбранную дату{{else if eq $p.Source "quik"}}Позиции и деньги в торговой системе, цены биржи{{else}}Портфель по брокерскому учёту{{end}}</span>
|
||||
</div>
|
||||
|
||||
{{if $p.Cash}}<section class="sec" style="margin-bottom:18px">
|
||||
<div class="sec-h"><h2 class="lbl" style="font-size:12px;letter-spacing:.14em">Денежные средства</h2></div>
|
||||
<div style="display:flex;gap:8px;flex-wrap:wrap">{{range $p.Cash}}<span class="chip" style="font-size:13px;text-transform:none;letter-spacing:0"><b class="n">{{fmtmoney .Amount}}</b> {{if eq .Currency "SUR"}}₽{{else}}{{.Currency}}{{end}}</span>{{end}}</div>
|
||||
</section>{{end}}
|
||||
|
||||
<section class="sec">
|
||||
<div class="sec-h"><h2 class="lbl" style="font-size:12px;letter-spacing:.14em">Реестр бумаг</h2><a href="/portfolio" style="font-size:13px;text-decoration:none">весь реестр</a></div>
|
||||
{{if .Positions}}<div class="tw"><table>
|
||||
<thead><tr><th>Выпуск</th><th>Счёт депо</th><th class="num">Количество</th><th class="num">Свободно</th><th class="num">Оценка</th><th>Состояние</th></tr></thead>
|
||||
<tbody>{{range .Positions}}<tr>
|
||||
<td><a href="/security?isin={{.ISIN}}" style="text-decoration:none;font-weight:600">{{.SecurityName}}</a><div class="n" style="font-size:12.5px;color:var(--mut)">{{.ISIN}}</div></td>
|
||||
<td class="n" style="font-size:13px">{{if .AccountID}}{{.AccountID}}{{else}}—{{end}}</td>
|
||||
<div class="sec-h"><h2 class="lbl" style="font-size:12px;letter-spacing:.14em">Бумаги</h2><span class="mut" style="font-size:12.5px">{{len $p.Positions}} позиций{{if $p.Positions}}, с ценой {{$p.Priced}}{{end}}</span></div>
|
||||
{{if $p.Positions}}<div class="tw"><table>
|
||||
<thead><tr><th>Бумага</th><th class="num">Кол-во</th><th class="num">Средняя</th><th class="num">Цена</th><th class="num">Стоимость</th><th class="num">Результат</th><th class="num">Доля</th></tr></thead>
|
||||
<tbody>{{range $p.Positions}}<tr>
|
||||
<td>{{if .ISIN}}<a href="/security?isin={{.ISIN}}" style="text-decoration:none;font-weight:600">{{.Name}}</a>{{else}}<b>{{.Name}}</b>{{end}}<div class="kind">{{.Kind}}{{if .ISIN}} · <span class="n" style="text-transform:none;letter-spacing:0">{{.ISIN}}</span>{{end}}{{if .Note}} · {{.Note}}{{end}}</div></td>
|
||||
<td class="num">{{fmtnum .Quantity}}</td>
|
||||
<td class="num">{{fmtnum .Available}}</td>
|
||||
<td class="num">{{if gt .AvgPrice 0.0}}{{fmtprice .AvgPrice}}{{if .InPct}}%{{end}}{{else}}—{{end}}</td>
|
||||
<td class="num">{{if gt .Price 0.0}}{{fmtprice .Price}}{{if .InPct}}%{{end}}{{if ne .DayPct 0.0}}<div class="{{if lt .DayPct 0.0}}neg{{else}}pos{{end}}" style="font-size:11.5px">{{fmtpct .DayPct}}</div>{{end}}{{else}}—{{end}}</td>
|
||||
<td class="num">{{template "money" .Value}}</td>
|
||||
<td>{{if not .Transferable}}<span class="chip">не переводится</span>{{else if gt .Blocked 0.0}}<span class="chip go">{{fmtnum .Blocked}} с ограничением</span>{{else}}<span class="chip ok">свободно</span>{{end}}</td>
|
||||
<td class="num">{{if ne .Cost 0.0}}<span class="{{if lt .PnL 0.0}}neg{{else if gt .PnL 0.0}}pos{{end}}">{{fmtsigned .PnL}}<div style="font-size:11.5px">{{fmtpct .PnLPct}}</div></span>{{else}}—{{end}}</td>
|
||||
<td class="num">{{if gt .Share 0.0}}{{fmtpct1 .Share}}{{else}}—{{end}}</td>
|
||||
</tr>{{end}}</tbody></table></div>
|
||||
{{else}}<p class="empty">Бумаг на счетах депо не найдено. Если это ошибка — напишите в депозитарий.</p>{{end}}
|
||||
{{else}}<p class="empty">{{if eq .View "archive"}}На эту дату бумаг на счёте не было.{{else}}Бумаг на счёте нет.{{end}}</p>{{end}}
|
||||
</section>
|
||||
</main></body></html>{{end}}
|
||||
|
||||
{{define "portfolio"}}{{template "head" .}}{{template "nav" .}}
|
||||
<section class="board"><div class="board-row">
|
||||
<div><span class="lbl" style="color:var(--dim)">Реестр бумаг</span>
|
||||
{{if gt .Value 0.0}}<div class="big" style="margin-top:8px;font-size:32px">{{fmtmoney .Value}} ₽</div>{{end}}
|
||||
<div class="n" style="font-size:13px;color:var(--dim);margin-top:6px">{{.Source}}{{with .UpdatedAt}} · {{.}}{{end}}{{if gt .Value 0.0}} · цены торговой системы{{end}}</div></div>
|
||||
<div class="stats">
|
||||
<span class="stat"><span class="lbl" style="color:var(--dim)">Выпусков</span><span class="v">{{.Total}}</span></span>
|
||||
<span class="stat"><span class="lbl" style="color:var(--dim)">Свободно к переводу</span><span class="v">{{.Free}}</span></span>
|
||||
<span class="stat"><span class="lbl" style="color:var(--dim)">С ограничением</span><span class="v" style="color:var(--amber)">{{.Blocked}}</span></span>
|
||||
</div>
|
||||
</div></section>
|
||||
<main>
|
||||
{{if .Flash}}<div class="msg ok">{{.Flash}}</div>{{end}}
|
||||
{{if .Error}}<div class="msg err">{{.Error}}</div>{{end}}
|
||||
{{if .ReadOnly}}<div class="msg">Кабинет открыт в режиме просмотра: подача поручений по этому договору выключена депозитарием.</div>{{end}}
|
||||
{{if .Positions}}
|
||||
<div class="tw"><table>
|
||||
<thead><tr><th>Выпуск</th><th>Счёт депо</th><th class="num">Количество</th><th class="num">С ограничением</th><th class="num">Свободно</th><th class="num">Цена</th><th class="num">Оценка</th><th>Состояние</th><th></th></tr></thead>
|
||||
<tbody>{{range .Positions}}<tr>
|
||||
<td><a href="/security?isin={{.ISIN}}" style="text-decoration:none;font-weight:600">{{.SecurityName}}</a><div class="n" style="font-size:12.5px;color:var(--mut)">{{.ISIN}}{{with .SecurityCode}} · {{.}}{{end}}</div></td>
|
||||
<td class="n" style="font-size:13px">{{if .AccountID}}{{.AccountID}}{{else}}—{{end}}</td>
|
||||
<td class="num">{{fmtnum .Quantity}}</td>
|
||||
<td class="num">{{if gt .Blocked 0.0}}<span style="color:var(--amber-tx)">{{fmtnum .Blocked}}</span>{{else}}—{{end}}</td>
|
||||
<td class="num">{{fmtnum .Available}}</td>
|
||||
<td class="num">{{if gt .Price 0.0}}{{fmtmoney .Price}}{{if ne .ChangePct 0.0}}<div style="font-size:12px;{{if gt .ChangePct 0.0}}color:var(--ok-tx){{else}}color:var(--err-tx){{end}}">{{fmtpct .ChangePct}}</div>{{end}}{{else}}—{{end}}</td>
|
||||
<td class="num">{{template "money" .Value}}</td>
|
||||
<td>{{if not .Transferable}}<span class="chip">иностранный выпуск</span>{{else if gt .Blocked 0.0}}<span class="chip go">частично ограничено</span>{{else}}<span class="chip ok">свободно</span>{{end}}{{with .Note}}<div class="mut">{{.}}</div>{{end}}</td>
|
||||
<td style="text-align:right">{{if and .Transferable (gt .Available 0.0) (not $.ReadOnly)}}<a href="/order?isin={{.ISIN}}" class="lbl" style="text-decoration:none">перевести</a>{{end}}</td>
|
||||
</tr>{{end}}</tbody></table></div>
|
||||
<p class="mut" style="margin-top:18px"><b>С ограничением</b> — бумаги в расчётах по сделке, под обременением или ограниченные в обращении: перевести их нельзя, пока ограничение не снято.
|
||||
<b>Иностранный выпуск</b> — перевод между депозитариями по MOEX МОСТ работает только с российскими выпусками, код ISIN начинается на RU.</p>
|
||||
{{else}}<p class="empty">Бумаг на счетах депо не найдено.</p>{{end}}
|
||||
</main></body></html>{{end}}
|
||||
|
||||
{{define "transfer"}}{{template "head" .}}{{template "nav" .}}
|
||||
<main>
|
||||
{{if .Error}}<div class="msg err">{{.Error}}</div>{{end}}
|
||||
@@ -520,81 +490,38 @@ input[type=checkbox]{width:20px;min-height:20px;height:20px}
|
||||
</main></body></html>{{end}}
|
||||
{{define "operations"}}{{template "head" .}}{{template "nav" .}}
|
||||
<section class="board"><div class="board-row">
|
||||
<div><span class="lbl" style="color:var(--dim)">Операции по счетам депо</span>
|
||||
<div class="big" style="margin-top:8px;font-size:32px">{{.Total}}</div>
|
||||
<div class="n" style="font-size:13px;color:var(--dim);margin-top:6px">за период {{.From}} — {{.To}}</div></div>
|
||||
<div>
|
||||
<span class="lbl" style="color:var(--dim)">Операции</span>
|
||||
<div class="big" style="margin-top:8px">{{len .Rows}}</div>
|
||||
<div class="n" style="font-size:13px;color:var(--dim);margin-top:6px">за период {{.From}} — {{.To}}</div>
|
||||
</div>
|
||||
<div class="stats">
|
||||
<span class="stat"><span class="lbl" style="color:var(--dim)">Зачислений</span><span class="v">{{.In}}</span></span>
|
||||
<span class="stat"><span class="lbl" style="color:var(--dim)">Списаний</span><span class="v">{{.Out}}</span></span>
|
||||
<span class="stat"><span class="lbl" style="color:var(--dim)">Покупки</span><span class="v">{{fmtmoney .BuyRUB}} ₽</span></span>
|
||||
<span class="stat"><span class="lbl" style="color:var(--dim)">Продажи</span><span class="v">{{fmtmoney .SellRUB}} ₽</span></span>
|
||||
<span class="stat"><span class="lbl" style="color:var(--dim)">Выплаты</span><span class="v">{{fmtmoney .IncomeRUB}} ₽</span></span>
|
||||
</div>
|
||||
</div></section>
|
||||
<main>
|
||||
{{if .Error}}<div class="msg err">{{.Error}}</div>{{end}}
|
||||
<form method="get" action="/operations" style="display:flex;gap:12px;align-items:flex-end;flex-wrap:wrap;margin-bottom:18px">
|
||||
<div><label for="from">Период с</label><input id="from" type="date" name="from" value="{{.From}}" style="width:auto"></div>
|
||||
<div><label for="to">по</label><input id="to" type="date" name="to" value="{{.To}}" style="width:auto"></div>
|
||||
<button class="btn" type="submit">Показать</button>
|
||||
{{if .Note}}<div class="msg ok">{{.Note}}</div>{{end}}
|
||||
<form method="get" action="/operations" class="tabform" style="margin:0 0 18px">
|
||||
<div><label for="from">Период с</label><input id="from" type="date" name="from" value="{{.From}}"></div>
|
||||
<div><label for="to">по</label><input id="to" type="date" name="to" value="{{.To}}"></div>
|
||||
<button class="btn sm" type="submit" style="align-self:flex-end">Показать</button>
|
||||
</form>
|
||||
{{if .Operations}}<div class="tw"><table>
|
||||
<thead><tr><th>Дата</th><th>Операция</th><th>Выпуск</th><th>Счёт депо</th><th class="num">Количество</th><th>Состояние</th><th>Основание</th></tr></thead>
|
||||
<tbody>{{range .Operations}}<tr>
|
||||
<td class="n">{{.Date}}{{if and .DoneAt (ne .DoneAt .Date)}}<div class="mut">проведено {{.DoneAt}}</div>{{end}}</td>
|
||||
<td>{{.Kind}}{{with .Number}}<div class="n mut">№ {{.}}</div>{{end}}</td>
|
||||
<td>{{if .ISIN}}<a href="/security?isin={{.ISIN}}" style="text-decoration:none">{{.Security}}</a><div class="n mut">{{.ISIN}}</div>{{else}}{{.Security}}{{end}}</td>
|
||||
<td class="n" style="font-size:13px">{{.Account}}</td>
|
||||
<td class="num">{{if lt .Quantity 0.0}}<span style="color:var(--err-tx)">{{fmtnum .Quantity}}</span>{{else}}<span style="color:var(--ok-tx)">+{{fmtnum .Quantity}}</span>{{end}}</td>
|
||||
<td>{{if eq .State "исполнено"}}<span class="chip ok">исполнено</span>{{else if eq .State "отменено"}}<span class="chip err">отменено</span>{{else}}<span class="chip go">{{.State}}</span>{{end}}</td>
|
||||
<td class="mut">{{if .Note}}{{.Note}}{{else}}—{{end}}</td>
|
||||
</tr>{{end}}</tbody></table></div>
|
||||
<p class="mut" style="margin-top:18px">Показаны зачисления, списания и переводы между вашими счетами депо по данным учёта депозитария. Выписку за период можно заказать у депозитария.</p>
|
||||
{{else}}<p class="empty">За выбранный период операций по счетам депо нет.</p>{{end}}
|
||||
</main></body></html>{{end}}
|
||||
|
||||
{{define "income"}}{{template "head" .}}{{template "nav" .}}
|
||||
<section class="board"><div class="board-row">
|
||||
<div><span class="lbl" style="color:var(--dim)">Выплачено дохода</span>
|
||||
<div class="big" style="margin-top:8px">{{fmtmoney .Paid}} ₽</div>
|
||||
<div class="n" style="font-size:13px;color:var(--dim);margin-top:6px">за период {{.From}} — {{.To}}</div></div>
|
||||
<div class="stats">
|
||||
<span class="stat"><span class="lbl" style="color:var(--dim)">Начислено</span><span class="v">{{fmtmoney .Accrued}} ₽</span></span>
|
||||
<span class="stat"><span class="lbl" style="color:var(--dim)">Удержан налог</span><span class="v" style="color:var(--amber)">{{fmtmoney .Tax}} ₽</span></span>
|
||||
</div>
|
||||
</div></section>
|
||||
<main>
|
||||
{{if .Error}}<div class="msg err">{{.Error}}</div>{{end}}
|
||||
<form method="get" action="/income" style="display:flex;gap:12px;align-items:flex-end;flex-wrap:wrap;margin-bottom:18px">
|
||||
<div><label for="from">Период с</label><input id="from" type="date" name="from" value="{{.From}}" style="width:auto"></div>
|
||||
<div><label for="to">по</label><input id="to" type="date" name="to" value="{{.To}}" style="width:auto"></div>
|
||||
<button class="btn" type="submit">Показать</button>
|
||||
</form>
|
||||
{{if .Income}}<div class="tw"><table>
|
||||
<thead><tr><th>Дата</th><th>Выпуск</th><th>Вид дохода</th><th class="num">Бумаг</th><th class="num">Начислено</th><th class="num">Налог</th><th class="num">К выплате</th></tr></thead>
|
||||
<tbody>{{range .Income}}<tr>
|
||||
{{if .Rows}}<div class="tw"><table>
|
||||
<thead><tr><th>Дата</th><th>Операция</th><th>Инструмент</th><th class="num">Кол-во</th><th class="num">Цена</th><th class="num">Сумма</th></tr></thead>
|
||||
<tbody>{{range .Rows}}<tr>
|
||||
<td class="n">{{.Date}}</td>
|
||||
<td>{{if .ISIN}}<a href="/security?isin={{.ISIN}}" style="text-decoration:none">{{.Security}}</a><div class="n mut">{{.ISIN}}</div>{{else}}{{.Security}}{{end}}</td>
|
||||
<td>{{.Kind}}</td>
|
||||
<td class="num">{{if gt .Quantity 0.0}}{{fmtnum .Quantity}}{{else}}—{{end}}</td>
|
||||
<td class="num">{{fmtmoney .Accrued}}</td>
|
||||
<td class="num">{{if gt .Tax 0.0}}{{fmtmoney .Tax}}{{else}}—{{end}}</td>
|
||||
<td class="num" style="font-weight:700">{{fmtmoney .Paid}} ₽</td>
|
||||
<td><span class="chip {{if eq .Side "in"}}ok{{else}}go{{end}}">{{.Kind}}</span></td>
|
||||
<td>{{if .ISIN}}<a href="/security?isin={{.ISIN}}" style="text-decoration:none;font-weight:600">{{.Name}}</a><div class="n" style="font-size:12px;color:var(--mut)">{{.ISIN}}</div>{{else}}{{.Name}}{{end}}</td>
|
||||
<td class="num">{{if ne .Quantity 0.0}}{{fmtnum .Quantity}}{{else}}—{{end}}</td>
|
||||
<td class="num">{{if gt .Price 0.0}}{{fmtprice .Price}}{{else}}—{{end}}</td>
|
||||
<td class="num"><span class="{{if eq .Side "in"}}pos{{else}}neg{{end}}">{{if eq .Side "in"}}+{{else}}−{{end}}{{fmtmoney .AmountRUB}} ₽</span>{{if and .Currency (ne .Currency "SUR")}}<div style="font-size:11.5px;color:var(--mut)">{{fmtmoney .Amount}} {{.Currency}}</div>{{end}}</td>
|
||||
</tr>{{end}}</tbody></table></div>
|
||||
<p class="mut" style="margin-top:18px">Суммы и удержанный налог — по данным учёта депозитария как налогового агента по выплатам эмитентов. По купонам государственных облигаций налог может удерживать брокер при выплате, тогда здесь он нулевой. Справку о доходах можно запросить у депозитария.</p>
|
||||
{{else}}<p class="empty">За выбранный период выплат дохода нет.</p>{{end}}
|
||||
|
||||
{{if .Actions}}<section class="sec" style="margin-top:30px">
|
||||
<h2 class="lbl" style="font-size:12px;letter-spacing:.14em">Корпоративные действия по вашим бумагам</h2>
|
||||
<div class="tw"><table>
|
||||
<thead><tr><th>Дата фиксации</th><th>Выпуск</th><th>Событие</th><th>Дата операции</th><th>Примечание</th></tr></thead>
|
||||
<tbody>{{range .Actions}}<tr>
|
||||
<td class="n">{{if .FixDate}}{{.FixDate}}{{else}}—{{end}}</td>
|
||||
<td>{{if .ISIN}}<a href="/security?isin={{.ISIN}}" style="text-decoration:none">{{.Security}}</a><div class="n mut">{{.ISIN}}</div>{{else}}{{.Security}}{{end}}</td>
|
||||
<td>{{.Kind}}</td>
|
||||
<td class="n">{{if .OperDate}}{{.OperDate}}{{else}}—{{end}}</td>
|
||||
<td class="mut">{{if .Note}}{{.Note}}{{else}}—{{end}}</td>
|
||||
</tr>{{end}}</tbody></table></div>
|
||||
<p class="mut" style="margin-top:14px">События по бумагам вашего портфеля из справочника депозитария. Если по действию нужно ваше решение — например, предъявить бумаги к выкупу по оферте, — подайте заявление через раздел «Документы» или обратитесь к своему менеджеру.</p>
|
||||
</section>{{end}}
|
||||
{{else}}<p class="empty">За выбранный период операций нет.</p>{{end}}
|
||||
</main></body></html>{{end}}
|
||||
|
||||
{{define "documents"}}{{template "head" .}}{{template "nav" .}}
|
||||
<section class="board"><div class="board-row">
|
||||
<div><span class="lbl" style="color:var(--dim)">Документы депозитария</span>
|
||||
|
||||
Reference in New Issue
Block a user