Кабинет: курсы ЦБ, история стоимости портфеля, быстрые операции, контакты в профиле
Курсы валют. Оценивать замещающие и еврооблигации по курсу из копии учётной системы было нельзя: в DEPO он от 31.05.2026, в SPECTRE — от 18.07.2026. Берём официальный курс Банка России (открытый XML, без ключа — тем же каналом пользуется Инвест-хаб) и складываем по дням в lk.fx_rates: без интернета кабинет считает по последнему известному курсу, а не отказывается оценивать. Источник переключается в настройках («Данные кабинета»), там же кнопка «Обновить курсы сейчас». Портфель БР-719: 560,3 млн ₽ против 522 млн по старому курсу. История стоимости. Ни QUIK, ни SPECTRE её не хранят, поэтому раз в день после закрытия торгов считаем стоимость каждого портфеля по экспорту QUIK и пишем строку в lk.portfolio_daily (257 портфелей за 14 секунд). Кабинет рисует по ним график на странице портфеля — SVG собирается на сервере, скриптов в кабинете по-прежнему нет. Режим по умолчанию — «автоматически»: ведём, если настроен экспорт QUIK, потому что задним числом историю не восстановить. Операции. Связка «JOIN OD_RESTS ON sr.ID = CASE WHEN w.D_REST IN (…)» заставляла SQL Server разбирать список счетов на каждой строке журнала проводок: год данных читался 6–7 секунд. Обе стороны проводки выбираются заранее двумя ветвями UNION ALL, индексы IX_WIRING_D_REST_WIRDATE и IX_WIRING_K_REST_WIRDATE отрабатывают поиском — 89 мс на тех же 64 строках. Период по умолчанию вернули к году, ответ кэшируется на пять минут. Профиль: почта, телефон и прошлый вход из анкеты — клиент видит, куда с ним свяжется депозитарий, и заметит устаревшие данные. Правка только через менеджера. Заказ документов из кабинета убран совсем (форма была убрана раньше, теперь нет и обработчика). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q4YVo4UMopqT5DzicqpPjL
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
package lkgateway
|
||||
|
||||
// lkdata.go — карточка «Данные кабинета» в настройках: откуда берём курсы валют
|
||||
// и ведём ли ежедневный срез стоимости портфелей (для графика у клиента).
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// LKDataView — состояние источников данных кабинета для страницы настроек.
|
||||
type LKDataView struct {
|
||||
FXSource string // cbr | accounting
|
||||
FXAt string // дата последнего известного курса
|
||||
FXCount int // сколько валют в последнем комплекте
|
||||
FXNote string // пояснение/ошибка связи с Банком России
|
||||
SnapshotMode string // "" (авто) | on | off
|
||||
SnapshotOn bool // ведём ли по факту
|
||||
SnapshotAt string // ЧЧ:ММ
|
||||
Snapshots int // сколько дней истории накоплено
|
||||
SnapLast string // последний снятый срез
|
||||
SnapRows int // строк в последнем срезе
|
||||
}
|
||||
|
||||
// lkDataView — собрать состояние без обращения в интернет (карточку открывают часто).
|
||||
func lkDataView(rc *RuntimeConfig) LKDataView {
|
||||
s := rc.Snapshot().LK
|
||||
hour, min := lkSnapshotTime(s)
|
||||
v := LKDataView{FXSource: strings.TrimSpace(s.FXSource), SnapshotMode: strings.TrimSpace(s.SnapshotMode),
|
||||
SnapshotOn: lkSnapshotEnabled(rc), SnapshotAt: fmt.Sprintf("%02d:%02d", hour, min)}
|
||||
if v.FXSource == "" {
|
||||
v.FXSource = "cbr"
|
||||
}
|
||||
db, err := lkDB(rc)
|
||||
if err != nil {
|
||||
v.FXNote = "база bj недоступна: " + err.Error()
|
||||
return v
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
var day *time.Time
|
||||
var n int
|
||||
if db.QueryRowContext(ctx, `SELECT max(day) FROM lk.fx_rates`).Scan(&day) == nil && day != nil {
|
||||
v.FXAt = day.Format("02.01.2006")
|
||||
_ = db.QueryRowContext(ctx, `SELECT count(*) FROM lk.fx_rates WHERE day = $1`, *day).Scan(&n)
|
||||
v.FXCount = n
|
||||
}
|
||||
var sday *time.Time
|
||||
if db.QueryRowContext(ctx, `SELECT count(DISTINCT day), max(day) FROM lk.portfolio_daily`).Scan(&v.Snapshots, &sday) == nil && sday != nil {
|
||||
v.SnapLast = sday.Format("02.01.2006")
|
||||
_ = db.QueryRowContext(ctx, `SELECT count(*) FROM lk.portfolio_daily WHERE day = $1`, *sday).Scan(&v.SnapRows)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// lkDataSave — POST /admin/setup/lk/data: источник курсов и ежедневный срез.
|
||||
func (h *setupHandlers) lkDataSave(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "method", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
switch r.FormValue("op") {
|
||||
case "fx_now": // проверить связь с Банком России и обновить курсы
|
||||
at, rates, err := cbrFetch(r.Context(), time.Time{})
|
||||
if err != nil {
|
||||
setupFlash(w, r, "Банк России недоступен: "+err.Error()+". Курсы останутся прежними.")
|
||||
return
|
||||
}
|
||||
lkFxSave(r.Context(), h.rc, at, "cbr", rates)
|
||||
lkFxReset()
|
||||
setupFlash(w, r, fmt.Sprintf("Курсы Банка России на %s получены: валют %d.", at.Format("02.01.2006"), len(rates)))
|
||||
return
|
||||
case "snapshot_now": // снять срез стоимости прямо сейчас
|
||||
go func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Minute)
|
||||
defer cancel()
|
||||
n, err := lkSnapshotRun(ctx, h.rc, time.Now())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_ = n
|
||||
}()
|
||||
setupFlash(w, r, "Срез стоимости портфелей снимается — на 1300 договоров это несколько минут; обновите страницу.")
|
||||
return
|
||||
}
|
||||
s := h.rc.Snapshot().LK
|
||||
if v := strings.TrimSpace(r.FormValue("fx_source")); v == "cbr" || v == "accounting" {
|
||||
s.FXSource = v
|
||||
}
|
||||
switch v := strings.TrimSpace(r.FormValue("snapshot_mode")); v {
|
||||
case "", "on", "off":
|
||||
s.SnapshotMode = v
|
||||
}
|
||||
if v := strings.TrimSpace(r.FormValue("snapshot_at")); len(v) == 5 && v[2] == ':' {
|
||||
s.SnapshotAt = v
|
||||
}
|
||||
if err := h.rc.UpdateLK(s); err != nil {
|
||||
setupFlash(w, r, "Данные кабинета: ошибка сохранения: "+err.Error())
|
||||
return
|
||||
}
|
||||
lkFxReset()
|
||||
src := "Банк России"
|
||||
if s.FXSource == "accounting" {
|
||||
src = "учётная система"
|
||||
}
|
||||
msg := "Курсы валют: " + src + "."
|
||||
if s.SnapshotMode == "off" {
|
||||
msg += " Ежедневный срез выключен — график в кабинете обновляться не будет."
|
||||
} else {
|
||||
hour, min := lkSnapshotTime(s)
|
||||
msg += fmt.Sprintf(" Срез стоимости портфелей — ежедневно в %02d:%02d.", hour, min)
|
||||
}
|
||||
setupFlash(w, r, msg)
|
||||
}
|
||||
Reference in New Issue
Block a user