Кабинет клиента: портфель как у брокера — «Онлайн» по 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:
@@ -7,6 +7,7 @@ import (
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@@ -271,6 +272,80 @@ func registerLKAPI(mux *http.ServeMux, rc *RuntimeConfig, svc *Service) {
|
||||
lkJSON(w, http.StatusOK, map[string]any{"operations": ops, "from": q.Get("from"), "to": q.Get("to")})
|
||||
})
|
||||
|
||||
// Портфель клиента как у брокера: view=online — по экспорту QUIK (что есть сейчас), если
|
||||
// в QUIK клиента нет — по брокерскому учёту на сегодня; view=archive&date=YYYY-MM-DD —
|
||||
// портфель на дату по брокерскому учёту (SPECTRE).
|
||||
mux.HandleFunc("/api/lk/portfolio2", func(w http.ResponseWriter, r *http.Request) {
|
||||
if !auth(w, r) {
|
||||
return
|
||||
}
|
||||
u, ok := current(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
fancy := rc.Snapshot().Fancy
|
||||
q := r.URL.Query()
|
||||
var pf *LKPortfolio
|
||||
var err error
|
||||
switch q.Get("view") {
|
||||
case "archive":
|
||||
day := time.Now()
|
||||
if d, e := time.ParseInLocation("2006-01-02", q.Get("date"), time.Local); e == nil {
|
||||
day = d
|
||||
}
|
||||
pf, err = lkArchivePortfolio(r.Context(), fancy, u.Contract, day)
|
||||
default:
|
||||
pf, err = lkOnlinePortfolio(r.Context(), rc, u.Contract)
|
||||
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 = "по данным брокерского учёта на сегодня"
|
||||
pf = ar
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
lkJSON(w, http.StatusBadGateway, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
pf.Note = strings.TrimSpace(pf.Note)
|
||||
lkJSON(w, http.StatusOK, map[string]any{"portfolio": pf, "contract": u.Contract, "full_name": u.FullName})
|
||||
})
|
||||
|
||||
// Операции по брокерскому счёту: сделки и движения бумаг (SPECTRE) плюс выплаты дохода (учёт).
|
||||
mux.HandleFunc("/api/lk/ops", func(w http.ResponseWriter, r *http.Request) {
|
||||
if !auth(w, r) {
|
||||
return
|
||||
}
|
||||
u, ok := current(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
fancy := rc.Snapshot().Fancy
|
||||
q := r.URL.Query()
|
||||
fromT, toT := parseRange(q.Get("from"), q.Get("to"))
|
||||
rows, err := lkArchiveOperations(r.Context(), fancy, u.Contract, fromT, toT)
|
||||
if err != nil {
|
||||
rows = nil // без брокерского учёта показываем хотя бы выплаты
|
||||
}
|
||||
if inc, _, e := lkIncome(r.Context(), fancy, u.Contract, q.Get("from"), q.Get("to")); e == nil {
|
||||
for _, p := range inc {
|
||||
rows = append(rows, LKOperationRow{Date: p.Date, Kind: p.Kind, Side: "in", Name: p.Security, ISIN: p.ISIN,
|
||||
Quantity: p.Quantity, Amount: p.Paid, Currency: "SUR", AmountRUB: p.Paid})
|
||||
}
|
||||
}
|
||||
sort.SliceStable(rows, func(i, j int) bool {
|
||||
di, _ := time.Parse("02.01.2006", rows[i].Date)
|
||||
dj, _ := time.Parse("02.01.2006", rows[j].Date)
|
||||
return di.After(dj)
|
||||
})
|
||||
resp := map[string]any{"operations": rows, "from": fromT.Format("2006-01-02"), "to": toT.Format("2006-01-02")}
|
||||
if err != nil {
|
||||
resp["note"] = err.Error()
|
||||
}
|
||||
lkJSON(w, http.StatusOK, resp)
|
||||
})
|
||||
|
||||
// Выплаты дохода и удержанный налог.
|
||||
mux.HandleFunc("/api/lk/income", func(w http.ResponseWriter, r *http.Request) {
|
||||
if !auth(w, r) {
|
||||
|
||||
Reference in New Issue
Block a user