Личный кабинет: оформление «Табло», разделы Табло/Реестр/Поручения/Профиль, поручение как вид
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+142
-22
@@ -14,6 +14,7 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -119,6 +120,7 @@ type position struct {
|
||||
ISIN string `json:"isin"`
|
||||
SecurityCode string `json:"security_code"`
|
||||
SecurityName string `json:"security_name"`
|
||||
AccountID string `json:"account_id"`
|
||||
Quantity float64 `json:"quantity"`
|
||||
Blocked float64 `json:"blocked"`
|
||||
Available float64 `json:"available"`
|
||||
@@ -137,6 +139,56 @@ type claimRow struct {
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
|
||||
// portfolioView — сводка реестра для доски: сколько выпусков всего, свободных и с ограничением.
|
||||
func portfolioView(pos []position) (total, free, blocked int, accounts []string) {
|
||||
seen := map[string]bool{}
|
||||
for _, p := range pos {
|
||||
total++
|
||||
switch {
|
||||
case p.Blocked > 0 || !p.Transferable:
|
||||
blocked++
|
||||
default:
|
||||
free++
|
||||
}
|
||||
if p.AccountID != "" && !seen[p.AccountID] {
|
||||
seen[p.AccountID] = true
|
||||
accounts = append(accounts, p.AccountID)
|
||||
}
|
||||
}
|
||||
sort.Strings(accounts)
|
||||
return
|
||||
}
|
||||
|
||||
// claimsView — сколько поручений в работе и сколько завершено.
|
||||
func claimsView(rows []claimRow) (active, done int) {
|
||||
for _, c := range rows {
|
||||
switch c.Status {
|
||||
case "done":
|
||||
done++
|
||||
case "rejected", "declined", "error":
|
||||
default:
|
||||
active++
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// portfolioResp — ответ /api/lk/portfolio.
|
||||
type portfolioResp struct {
|
||||
Contract string `json:"contract"`
|
||||
FullName string `json:"full_name"`
|
||||
Positions []position `json:"positions"`
|
||||
Source string `json:"source"`
|
||||
ReadOnly bool `json:"read_only"`
|
||||
}
|
||||
|
||||
// portfolio — реестр клиента из ядра.
|
||||
func (s *Server) portfolio(r *http.Request, sess string) (portfolioResp, error) {
|
||||
var out portfolioResp
|
||||
err := s.bj.call(r.Context(), http.MethodGet, "/api/lk/portfolio", sess, nil, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// ---- страницы ----
|
||||
|
||||
// render — страница; признаки режима (админ / просмотр кабинета клиента) — из кук этого запроса.
|
||||
@@ -238,7 +290,7 @@ func (s *Server) Handler() http.Handler {
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
})
|
||||
|
||||
// Портфель
|
||||
// Табло — обзор: реестр и последние поручения на одной странице.
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/" {
|
||||
http.NotFound(w, r)
|
||||
@@ -249,26 +301,88 @@ func (s *Server) Handler() http.Handler {
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
var out struct {
|
||||
Contract string `json:"contract"`
|
||||
FullName string `json:"full_name"`
|
||||
Positions []position `json:"positions"`
|
||||
Source string `json:"source"`
|
||||
ReadOnly bool `json:"read_only"`
|
||||
}
|
||||
if err := bj.call(r.Context(), http.MethodGet, "/api/lk/portfolio", sess, nil, &out); err != nil {
|
||||
s.render(w, r, "portfolio", map[string]any{"Nav": "p", "Error": err.Error()})
|
||||
pf, err := s.portfolio(r, sess)
|
||||
if err != nil {
|
||||
s.render(w, r, "board", map[string]any{"Nav": "b", "Error": err.Error()})
|
||||
return
|
||||
}
|
||||
s.render(w, r, "portfolio", map[string]any{
|
||||
"Nav": "p", "Contract": out.Contract, "FullName": out.FullName,
|
||||
"Positions": out.Positions, "Flash": r.URL.Query().Get("m"),
|
||||
"Source": out.Source, "ReadOnly": out.ReadOnly,
|
||||
total, free, blocked, _ := portfolioView(pf.Positions)
|
||||
var cl struct {
|
||||
Claims []claimRow `json:"claims"`
|
||||
}
|
||||
_ = bj.call(r.Context(), 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, "Flash": r.URL.Query().Get("m"),
|
||||
"Total": total, "Free": free, "Blocked": blocked, "Active": active,
|
||||
})
|
||||
})
|
||||
|
||||
// Заявка на перевод
|
||||
// Реестр бумаг
|
||||
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, "Flash": r.URL.Query().Get("m"),
|
||||
"Source": pf.Source, "ReadOnly": pf.ReadOnly, "UpdatedAt": updated,
|
||||
"Total": total, "Free": free, "Blocked": blocked,
|
||||
})
|
||||
})
|
||||
|
||||
// Профиль: договор, счета депо, доступ
|
||||
mux.HandleFunc("/profile", 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, "profile", map[string]any{"Nav": "u", "Error": err.Error()})
|
||||
return
|
||||
}
|
||||
_, _, _, accounts := portfolioView(pf.Positions)
|
||||
login := ""
|
||||
if c, err := r.Cookie("bj_lk_as"); err == nil {
|
||||
login = c.Value
|
||||
}
|
||||
if login == "" {
|
||||
login = pf.Contract
|
||||
}
|
||||
s.render(w, r, "profile", map[string]any{
|
||||
"Nav": "u", "Contract": pf.Contract, "FullName": pf.FullName,
|
||||
"AccountList": accounts, "Accounts": len(accounts), "ReadOnly": pf.ReadOnly, "Login": login,
|
||||
})
|
||||
})
|
||||
|
||||
// Поручение депозитарию (сейчас — перевод бумаг в другой депозитарий)
|
||||
mux.HandleFunc("/transfer", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "/order", http.StatusMovedPermanently)
|
||||
})
|
||||
mux.HandleFunc("/order", func(w http.ResponseWriter, r *http.Request) {
|
||||
sess := sessionW(w, r)
|
||||
if sess == "" {
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
@@ -280,10 +394,11 @@ func (s *Server) Handler() http.Handler {
|
||||
FullName string `json:"full_name"`
|
||||
}
|
||||
if err := bj.call(r.Context(), http.MethodGet, "/api/lk/portfolio", sess, nil, &out); err != nil {
|
||||
s.render(w, r, "transfer", map[string]any{"Nav": "t", "Error": err.Error()})
|
||||
s.render(w, r, "transfer", map[string]any{"Nav": "c", "Error": err.Error(), "Today": time.Now().Format("02.01.2006")})
|
||||
return
|
||||
}
|
||||
s.render(w, r, "transfer", map[string]any{"Nav": "t", "Positions": out.Positions, "FullName": out.FullName})
|
||||
s.render(w, r, "transfer", map[string]any{"Nav": "c", "Positions": out.Positions, "FullName": out.FullName,
|
||||
"PickISIN": r.URL.Query().Get("isin"), "Today": time.Now().Format("02.01.2006")})
|
||||
return
|
||||
}
|
||||
_ = r.ParseForm()
|
||||
@@ -300,7 +415,7 @@ func (s *Server) Handler() http.Handler {
|
||||
secs = append(secs, map[string]any{"isin": isin, "quantity": f})
|
||||
}
|
||||
if len(secs) == 0 {
|
||||
s.render(w, r, "transfer", map[string]any{"Nav": "t", "Error": "Укажите количество хотя бы по одной бумаге"})
|
||||
s.render(w, r, "transfer", map[string]any{"Nav": "c", "Error": "Укажите количество хотя бы по одной бумаге", "Today": time.Now().Format("02.01.2006")})
|
||||
return
|
||||
}
|
||||
var res struct {
|
||||
@@ -315,14 +430,17 @@ func (s *Server) Handler() http.Handler {
|
||||
"securities": secs,
|
||||
}, &res)
|
||||
if err != nil {
|
||||
s.render(w, r, "transfer", map[string]any{"Nav": "t", "Error": err.Error()})
|
||||
s.render(w, r, "transfer", map[string]any{"Nav": "c", "Error": err.Error(), "Today": time.Now().Format("02.01.2006")})
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, "/claims?m="+url.QueryEscape(res.Message), http.StatusSeeOther)
|
||||
http.Redirect(w, r, "/orders?m="+url.QueryEscape(res.Message), http.StatusSeeOther)
|
||||
})
|
||||
|
||||
// Мои заявки
|
||||
// Поручения — журнал всех видов
|
||||
mux.HandleFunc("/claims", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "/orders", http.StatusMovedPermanently)
|
||||
})
|
||||
mux.HandleFunc("/orders", func(w http.ResponseWriter, r *http.Request) {
|
||||
sess := sessionW(w, r)
|
||||
if sess == "" {
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
@@ -335,7 +453,9 @@ func (s *Server) Handler() http.Handler {
|
||||
s.render(w, r, "claims", map[string]any{"Nav": "c", "Error": err.Error()})
|
||||
return
|
||||
}
|
||||
s.render(w, r, "claims", map[string]any{"Nav": "c", "Claims": out.Claims, "Flash": r.URL.Query().Get("m")})
|
||||
active, done := claimsView(out.Claims)
|
||||
s.render(w, r, "claims", map[string]any{"Nav": "c", "Claims": out.Claims, "Flash": r.URL.Query().Get("m"),
|
||||
"Total": len(out.Claims), "Active": active, "Done": done})
|
||||
})
|
||||
|
||||
// Администратор: список клиентов
|
||||
|
||||
Reference in New Issue
Block a user