Files
Bridge-and-Join-s/internal/lkgateway/lkfx.go
T
fontvielle 312e4f70f1 ЛК на Fancy с Firebird: все запросы к DEPO, курсы, база SPECTRE
ЛК (bj-lk берёт данные у bj-server) сам работает с той СУБД, что выбрана для DEPO.
- Перевод на Firebird: YEAR/MONTH/DAY(x) — EXTRACT, AS float — AS DOUBLE PRECISION (float T-SQL — 8 байт,
  FLOAT Firebird — 4: точность сумм и цен в ЛК); шаблоны со сложением строк (история операций Hist02, поиск
  клиента Man01) — Firebird-тексты до подстановки (depoSQL); UPPER по кириллице — в WIN1251 (база NONE).
- Курсы валют (Fx01): на Firebird запрос от всех курсов шёл больше 200 с (лимит 15 с — курсов нет) — для
  Firebird от 20 валют, 0,2 с.
- База SPECTRE (архив портфеля и операций ЛК): поле в «Трансферные таблицы DEPO»; у Firebird — путь или строка
  подключения Fancy (может быть на другом компьютере); не задана — ЛК пишет, где указать. На данных SPECTRE
  не проверено: базы нет.
- Тест FBLiveLK: функции ЛК, ручного ввода, сверки переводов и QUIK на копии базы по договору К1 Бубликова —
  все работают, 0,07–0,26 с каждая; FBLivePrepare — 101 из 101 запроса.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn
2026-10-27 20:00:28 +03:00

130 lines
4.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package lkgateway
// lkfx.go — курсы валют к рублю для оценки валютных выпусков.
//
// Номинал «замещающих» и еврооблигаций в экспорте QUIK задан в валюте (FaceUnit = USD, EUR,
// CNY), а цена — в процентах от него. Чтобы показать стоимость в рублях, нужен курс. В базе
// экспорта курсов нет (валютный рынок не выгружается), поэтому берём официальный курс из
// учётной системы: OD_VALUES_RATES с MARKET = 0 — сначала DEPO, затем SPECTRE, и берём более
// свежий. Дата курса показывается клиенту: курс может отставать, и выдавать оценку за
// сегодняшнюю нельзя.
import (
"context"
"database/sql"
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/lkgateway/fsql"
"strings"
"sync"
"time"
)
// LKFxRate — курс валюты к рублю и дата, на которую он установлен.
type LKFxRate struct {
Rate float64
At time.Time
}
var fxCache = struct {
mu sync.Mutex
at time.Time
m map[string]LKFxRate
}{}
// lkFxRates — курсы валют для оценки (ключ — код валюты в верхнем регистре).
// Источник по умолчанию — Банк России (см. cbrfx.go); если интернета нет или
// в настройках выбрана учётная система, берём OD_VALUES_RATES, а недостающие
// валюты дополняем последним известным курсом из lk.fx_rates.
func lkFxRates(ctx context.Context, rc *RuntimeConfig) map[string]LKFxRate {
set := rc.Snapshot()
out := map[string]LKFxRate{}
if !strings.EqualFold(strings.TrimSpace(set.LK.FXSource), "accounting") {
if cbr, cerr := cbrToday(ctx, rc); len(cbr) > 0 {
return cbr
} else if cerr != "" {
// молча уходим на запасные источники: кабинет не должен падать
// из-за недоступного сайта ЦБ
_ = cerr
}
}
for code, r := range lkFxAccounting(ctx, set.Fancy) {
out[code] = r
}
for code, r := range lkFxLoad(ctx, rc, time.Time{}) {
if old, ok := out[code]; !ok || r.At.After(old.At) {
out[code] = r
}
}
return out
}
// lkFxAccounting — курсы из учётной системы (OD_VALUES_RATES, MARKET = 0), кэш на час.
func lkFxAccounting(ctx context.Context, s FancyMSSQLSettings) map[string]LKFxRate {
fxCache.mu.Lock()
if fxCache.m != nil && time.Since(fxCache.at) < time.Hour {
m := fxCache.m
fxCache.mu.Unlock()
return m
}
fxCache.mu.Unlock()
out := map[string]LKFxRate{}
if strings.TrimSpace(s.Server) == "" {
return out
}
for _, open := range []func() (*sql.DB, error){
func() (*sql.DB, error) { return fancyOpen(s, s.DepoDB()) },
func() (*sql.DB, error) { return spectreOpen(s) },
} {
conn, err := open()
if err != nil {
continue
}
cctx, cancel := context.WithTimeout(ctx, 15*time.Second)
rows, err := conn.QueryContext(cctx, fsql.Fx01)
if err == nil {
for rows.Next() {
var code string
var at sql.NullTime
var rate float64
if rows.Scan(&code, &at, &rate) != nil || rate <= 0 {
continue
}
cur := LKFxRate{Rate: rate}
if at.Valid {
cur.At = at.Time
}
// из двух баз берём курс посвежее
if old, ok := out[code]; !ok || cur.At.After(old.At) {
out[code] = cur
}
}
rows.Close()
}
cancel()
conn.Close()
}
fxCache.mu.Lock()
fxCache.m, fxCache.at = out, time.Now()
fxCache.mu.Unlock()
return out
}
// lkFxFor — курс валюты к рублю: рубль — единица, неизвестная валюта — 0 (оценивать нельзя).
func lkFxFor(rates map[string]LKFxRate, cur string) LKFxRate {
c := strings.ToUpper(strings.TrimSpace(cur))
switch c {
case "", "SUR", "RUB", "RUR":
return LKFxRate{Rate: 1}
}
return rates[c]
}
// lkFxReset — сбросить кэши курсов (после смены источника или ручного обновления).
func lkFxReset() {
fxCache.mu.Lock()
fxCache.m, fxCache.at = nil, time.Time{}
fxCache.mu.Unlock()
cbrCache.mu.Lock()
cbrCache.at, cbrCache.err = time.Time{}, ""
cbrCache.mu.Unlock()
}