Портфель ЛК: номер гос. регистрации из DEPO, пустой портфель не шлём, итог «Залить» на месте
Номер гос. регистрации — из DEPO (OD_SHARES.NUM_REG, выпуск бумаги), экспорт QUIK — запасной: без подключённого экспорта (модуль НПР) все бумаги пропускались. Портфель, где отправлять нечего (у всех бумаг нет номера — иностранные, КСУ), в ЛК не уходит: ЛК отвечал на пустой импорт HTTP 500. После «Залить сейчас» / «Включить» — назад в тот же раздел к карточке с сообщением об итоге (раньше #lk-portfolio открывал «Обзор»). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn
This commit is contained in:
@@ -89,18 +89,21 @@ func lkPortfolioLines(ctx context.Context, rc *RuntimeConfig, depoPort string, o
|
||||
}
|
||||
cfi, reg, cached := lkSecInfoCached(isin)
|
||||
if !cached {
|
||||
_ = db.QueryRowContext(ctx, "SELECT TOP 1 ISNULL(CFI,'') FROM OD_VALUES WHERE ISIN=@i ORDER BY ID", sql.Named("i", isin)).Scan(&cfi)
|
||||
if qerr == nil {
|
||||
// Номер гос. регистрации — из DEPO (OD_SHARES.NUM_REG — выпуск бумаги, OD_SHARES.ID = OD_VALUES.ID),
|
||||
// запасной — экспорт QUIK. Проверено на DEPO 28: у Сбербанка 10301481B, у ОФЗ 26222RMFS.
|
||||
_ = db.QueryRowContext(ctx, `SELECT TOP 1 ISNULL(v.CFI,''), ISNULL(s.NUM_REG,'') FROM OD_VALUES v LEFT JOIN OD_SHARES s ON s.ID=v.ID
|
||||
WHERE v.ISIN=@i ORDER BY CASE WHEN ISNULL(s.NUM_REG,'')<>'' THEN 0 ELSE 1 END, v.ID`, sql.Named("i", isin)).Scan(&cfi, ®)
|
||||
if strings.TrimSpace(reg) == "" && qerr == nil {
|
||||
_ = qe.QueryRow(ctx, `SELECT "RegNumber" FROM "Securities" WHERE "ISINCode"=$1 AND COALESCE("RegNumber",'')<>'' LIMIT 1`, isin).Scan(®)
|
||||
}
|
||||
if qerr == nil {
|
||||
if strings.TrimSpace(reg) != "" || qerr == nil {
|
||||
lkSecInfoPut(isin, cfi, reg)
|
||||
}
|
||||
}
|
||||
if strings.TrimSpace(reg) == "" {
|
||||
why := "нет номера гос. регистрации в экспорте QUIK"
|
||||
why := "нет номера гос. регистрации ни в DEPO, ни в экспорте QUIK (иностранные бумаги, КСУ)"
|
||||
if qerr != nil {
|
||||
why = "экспорт QUIK недоступен: " + qerr.Error()
|
||||
why = "нет номера гос. регистрации в DEPO (экспорт QUIK не подключён)"
|
||||
}
|
||||
skipped = append(skipped, fmt.Sprintf("%s %s (%s): %s", isin, p.SecurityName, p.AccountNum, why))
|
||||
continue
|
||||
@@ -260,6 +263,12 @@ func lkPortfolioSyncTick(ctx context.Context, rc *RuntimeConfig, force bool) {
|
||||
if same && !force {
|
||||
continue
|
||||
}
|
||||
if len(lines) == 0 { // ЛК отвечает 500 на пустой импорт — не шлём
|
||||
lkPortfolioState.Lock()
|
||||
s.Skipped, s.Err, s.hash = skipped, lkPortfolioNothing(skipped), h
|
||||
lkPortfolioState.Unlock()
|
||||
continue
|
||||
}
|
||||
code, resp, perr := lkPortfolioPush(ctx, rc, pr.LK, lines)
|
||||
lkPortfolioState.Lock()
|
||||
s.Skipped = skipped
|
||||
@@ -348,17 +357,41 @@ func registerLKPortfolioSync(mux *http.ServeMux, rc *RuntimeConfig) {
|
||||
}
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 2*time.Minute)
|
||||
lkPortfolioPushPair(ctx, rc, pr)
|
||||
n, skipped, perr := lkPortfolioPushPair(ctx, rc, pr)
|
||||
cancel()
|
||||
http.Redirect(w, r, "/admin/setup#lk-portfolio", http.StatusSeeOther)
|
||||
msg := fmt.Sprintf("Портфель залит в ЛК: договор %s ← портфель DEPO %s, бумаг %d", lkc, port, n)
|
||||
if skipped > 0 {
|
||||
msg += fmt.Sprintf(" (пропущено %d — без номера гос. регистрации)", skipped)
|
||||
}
|
||||
if perr != "" {
|
||||
msg = "Портфель не залит в ЛК (" + lkc + " ← " + port + "): " + perr
|
||||
}
|
||||
if r.FormValue("action") == "client_add" {
|
||||
msg += ". Клиент добавлен в постоянное заполнение."
|
||||
}
|
||||
lkPortfolioDone(w, r, msg)
|
||||
return
|
||||
}
|
||||
if a := r.FormValue("action"); a == "sync" || a == "on" || a == "on_all" { // включили или «Заполнить сейчас» — сразу отправить
|
||||
msg := "Настройки заполнения портфеля сохранены"
|
||||
switch a := r.FormValue("action"); a {
|
||||
case "sync", "on", "on_all": // включили или «Заполнить сейчас» — сразу отправить
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 2*time.Minute)
|
||||
lkPortfolioSyncTick(ctx, rc, true)
|
||||
cancel()
|
||||
ok, bad := 0, 0
|
||||
for _, st := range lkPortfolioStates() {
|
||||
if st.Err != "" {
|
||||
bad++
|
||||
} else if !st.PushedAt.IsZero() {
|
||||
ok++
|
||||
}
|
||||
}
|
||||
msg = map[string]string{"on": "Заполнение по списку включено", "on_all": "Заполнение для всех клиентов включено", "sync": "Портфели сверены"}[a] +
|
||||
fmt.Sprintf(": залито договоров %d, с ошибкой %d — подробности под карточкой", ok, bad)
|
||||
case "off":
|
||||
msg = "Заполнение портфеля выключено"
|
||||
}
|
||||
http.Redirect(w, r, "/admin/setup#lk-portfolio", http.StatusSeeOther)
|
||||
lkPortfolioDone(w, r, msg)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -458,8 +491,9 @@ func lkPortfolioAllPairs(ctx context.Context, rc *RuntimeConfig) ([]lkPortfolioP
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
// lkPortfolioPushPair — залить портфель одного клиента сейчас (тест / «Залить сейчас»).
|
||||
func lkPortfolioPushPair(ctx context.Context, rc *RuntimeConfig, pr lkPortfolioPair) {
|
||||
// lkPortfolioPushPair — залить портфель одного клиента сейчас (тест / «Залить сейчас»): сколько бумаг
|
||||
// ушло, сколько пропущено, ошибка (пусто — залито).
|
||||
func lkPortfolioPushPair(ctx context.Context, rc *RuntimeConfig, pr lkPortfolioPair) (int, int, string) {
|
||||
lines, skipped, err := lkPortfolioLines(ctx, rc, pr.Depo, nil)
|
||||
lkPortfolioState.Lock()
|
||||
s := lkPortfolioState.m[pr.LK]
|
||||
@@ -472,6 +506,8 @@ func lkPortfolioPushPair(ctx context.Context, rc *RuntimeConfig, pr lkPortfolioP
|
||||
msg := ""
|
||||
if err != nil {
|
||||
msg = err.Error()
|
||||
} else if len(lines) == 0 {
|
||||
msg = lkPortfolioNothing(skipped)
|
||||
} else if code, resp, perr := lkPortfolioPush(ctx, rc, pr.LK, lines); perr != nil {
|
||||
msg = perr.Error()
|
||||
} else if code != 200 || !strings.Contains(resp, `"success":true`) {
|
||||
@@ -490,4 +526,19 @@ func lkPortfolioPushPair(ctx context.Context, rc *RuntimeConfig, pr lkPortfolioP
|
||||
} else {
|
||||
log.Printf("lk-portfolio: %s ← %s: портфель ДЕПО залит в ЛК (%d бумаг, пропущено %d)", pr.LK, pr.Depo, len(lines), len(skipped))
|
||||
}
|
||||
return len(lines), len(skipped), msg
|
||||
}
|
||||
|
||||
// lkPortfolioNothing — почему портфель не отправлен: отправлять нечего.
|
||||
func lkPortfolioNothing(skipped []string) string {
|
||||
if len(skipped) == 0 {
|
||||
return "отправлять нечего: в DEPO у клиента нет бумаг на остатке"
|
||||
}
|
||||
return fmt.Sprintf("отправлять нечего: у всех бумаг (%d) нет номера гос. регистрации, без него ЛК не принимает портфель", len(skipped))
|
||||
}
|
||||
|
||||
// lkPortfolioDone — вернуться к карточке «Заполнение портфеля» в том же разделе настроек с сообщением.
|
||||
func lkPortfolioDone(w http.ResponseWriter, r *http.Request, msg string) {
|
||||
r.Form.Set("section", "lk-portfolio")
|
||||
setupFlash(w, r, msg)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user