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) }