DEPO: установка процедур bj2depo из bj (проверка возможностей + применение)

Процедуры FTV_M2M_PROCESS_* и FTV_M2M_JOB встроены в bj (depoprocs/*.sql).
В разделе «Учёт DEPO» карточка «Процедуры обработки bj2depo»:
- «Проверить возможности» — подключение, это ли база DEPO, какие процедуры
  установлены, есть ли право CREATE PROCEDURE и логин bj в базе (ничего не меняет);
- «Установить» — логином администратора SQL (разово, не сохраняется) выполняет
  CREATE OR ALTER в порядке WIRING→OPERATION→EVENTS→CREATE→JOB и GRANT EXECUTE
  на FTV_M2M_JOB логину bj; без прав — отказ без изменений;
- скачивание единого скрипта для ручной установки в SSMS.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn
This commit is contained in:
fontvielle
2026-10-20 13:56:40 +03:00
parent 63fbb64012
commit 045dfac6c7
8 changed files with 1134 additions and 0 deletions
+239
View File
@@ -0,0 +1,239 @@
package lkgateway
// depo_procs.go — установка процедур обработки bj2depo (FTV_M2M_PROCESS_* и FTV_M2M_JOB)
// в базу DEPO прямо из bj: «Проверить возможности» (предпросмотр без изменений) и
// «Установить» (выполняется логином администратора SQL — разово, не сохраняется).
// Процедуры встроены в bj (internal/lkgateway/depoprocs/*.sql), порядок установки —
// WIRING → OPERATION → EVENTS → CREATE → JOB; затем GRANT EXECUTE на FTV_M2M_JOB логину bj.
import (
"context"
"database/sql"
"embed"
"fmt"
"log"
"net/http"
"regexp"
"strings"
"time"
)
//go:embed depoprocs/*.sql
var depoProcFS embed.FS
// depoProcInstallOrder — имя процедуры и файл; порядок важен для зависимостей.
var depoProcInstallOrder = []struct{ Name, File string }{
{"FTV_M2M_PROCESS_WIRING", "depoprocs/FTV_M2M_PROCESS_WIRING.sql"},
{"FTV_M2M_PROCESS_OPERATION", "depoprocs/FTV_M2M_PROCESS_OPERATION.sql"},
{"FTV_M2M_PROCESS_EVENTS", "depoprocs/FTV_M2M_PROCESS_EVENTS.sql"},
{"FTV_M2M_PROCESS_CREATE", "depoprocs/FTV_M2M_PROCESS_CREATE.sql"},
{"FTV_M2M_JOB", "depoprocs/FTV_M2M_JOB.sql"},
}
var goBatchRe = regexp.MustCompile(`(?im)^\s*GO\s*$`)
// splitSQLBatches — разбить скрипт на батчи по строкам «GO» (директива клиента, не T-SQL).
func splitSQLBatches(s string) []string {
var out []string
for _, part := range goBatchRe.Split(s, -1) {
if strings.TrimSpace(part) != "" {
out = append(out, part)
}
}
return out
}
// depoProcFile — тело встроенной процедуры.
func depoProcFile(file string) (string, error) {
b, err := depoProcFS.ReadFile(file)
if err != nil {
return "", err
}
return string(b), nil
}
// depoProcsCombined — единый скрипт установки (для скачивания администратору): USE, процедуры, GRANT.
func depoProcsCombined(f FancyMSSQLSettings) string {
var b strings.Builder
login := depoSetupLogin(f)
b.WriteString("/* Bridge&Join: установка процедур обработки bj2depo в базу DEPO.\n")
b.WriteString(" Порядок WIRING -> OPERATION -> EVENTS -> CREATE -> JOB. Запуск под sysadmin в SSMS.\n")
b.WriteString(" CREATE OR ALTER — повторный запуск безопасен (данные не меняются). */\n")
fmt.Fprintf(&b, "USE %s;\nGO\n\n", quoteIdent(f.DepoDB()))
for _, p := range depoProcInstallOrder {
body, err := depoProcFile(p.File)
if err != nil {
continue
}
fmt.Fprintf(&b, "/* ===== %s ===== */\n%s\nGO\n\n", p.Name, strings.TrimRight(body, "\n"))
}
fmt.Fprintf(&b, "/* Право запуска задания логину bj */\nGRANT EXECUTE ON dbo.FTV_M2M_JOB TO %s;\nGO\n", quoteIdent(login))
return b.String()
}
// quoteIdent — квадратные скобки для идентификатора T-SQL.
func quoteIdent(s string) string { return "[" + strings.ReplaceAll(s, "]", "]]") + "]" }
// depoProcsPreflight — проверка возможности установки (без изменений в базе). Возвращает отчёт
// для экрана, признак «можно применять» и ошибку подключения/проверки.
func depoProcsPreflight(ctx context.Context, f FancyMSSQLSettings, adminUser, adminPass string) (report string, canApply bool, err error) {
if f.IsPostgres() {
return "", false, fmt.Errorf("установка процедур bj2depo поддержана только для MSSQL")
}
conn := f
admin := strings.TrimSpace(adminUser)
if admin != "" {
conn.User, conn.Password = admin, adminPass
}
db, err := fancyOpen(conn, f.DepoDB())
if err != nil {
return "", false, err
}
defer db.Close()
if err := db.PingContext(ctx); err != nil {
return "", false, fmt.Errorf("подключение к базе %s логином %s: %s", f.DepoDB(), conn.User, quikErrorDetail(err))
}
if err := depoCheckIsDepo(ctx, conn, f.DepoDB()); err != nil {
return "", false, err
}
missing := depoMissing(ctx, db, depoBj2depoProcs, "P")
login := depoSetupLogin(f)
var canCreate, sysadmin, bjUser int
_ = db.QueryRowContext(ctx,
`SELECT CAST(ISNULL(HAS_PERMS_BY_NAME(NULL,'DATABASE','CREATE PROCEDURE'),0) AS int),
CAST(ISNULL(IS_SRVROLEMEMBER('sysadmin'),0) AS int),
CASE WHEN DATABASE_PRINCIPAL_ID(@u) IS NULL THEN 0 ELSE 1 END`,
sql.Named("u", login)).Scan(&canCreate, &sysadmin, &bjUser)
canApply = canCreate == 1 || sysadmin == 1
var b strings.Builder
fmt.Fprintf(&b, "Сервер %s, база %s, логин %s.\n", f.Server, f.DepoDB(), conn.User)
if len(missing) == 0 {
b.WriteString("Процедуры: все 5 уже установлены (будут обновлены CREATE OR ALTER).\n")
} else {
fmt.Fprintf(&b, "Процедуры: нет %d из 5 (%s).\n", len(missing), strings.Join(missing, ", "))
}
if canApply {
b.WriteString("Право создавать процедуры: есть.\n")
} else {
b.WriteString("Право создавать процедуры: НЕТ — введите логин администратора SQL (sysadmin, обычно sa).\n")
}
if bjUser == 1 {
fmt.Fprintf(&b, "Логин %s в базе: есть — выдам ему EXECUTE на FTV_M2M_JOB.\n", login)
} else {
fmt.Fprintf(&b, "Логин %s в базе: нет — сначала подготовьте базу (логин/таблицы), GRANT будет пропущен.\n", login)
}
b.WriteString("Будет выполнено: CREATE OR ALTER 5 процедур (WIRING→OPERATION→EVENTS→CREATE→JOB) + GRANT EXECUTE на FTV_M2M_JOB.")
return b.String(), canApply, nil
}
// depoProcsInstall — установить процедуры логином администратора. Сначала preflight; при отсутствии
// прав — отказ без изменений. Возвращает итоговое сообщение.
func depoProcsInstall(ctx context.Context, f FancyMSSQLSettings, adminUser, adminPass string) (string, error) {
report, canApply, err := depoProcsPreflight(ctx, f, adminUser, adminPass)
if err != nil {
return "", err
}
if !canApply {
return "", fmt.Errorf("нет прав на установку. %s", report)
}
conn := f
if u := strings.TrimSpace(adminUser); u != "" {
conn.User, conn.Password = u, adminPass
}
db, err := fancyOpen(conn, f.DepoDB())
if err != nil {
return "", err
}
defer db.Close()
for _, p := range depoProcInstallOrder {
body, err := depoProcFile(p.File)
if err != nil {
return "", fmt.Errorf("встроенный файл %s: %w", p.File, err)
}
for _, batch := range splitSQLBatches(body) {
if _, err := db.ExecContext(ctx, batch); err != nil {
return "", fmt.Errorf("установка %s: %s", p.Name, quikErrorDetail(err))
}
}
}
msg := "установлено процедур: 5 (WIRING, OPERATION, EVENTS, CREATE, JOB)"
login := depoSetupLogin(f)
if depoLoginRe.MatchString(login) {
if _, err := db.ExecContext(ctx, "GRANT EXECUTE ON dbo.FTV_M2M_JOB TO "+quoteIdent(login)); err != nil {
msg += "; право EXECUTE логину " + login + " не выдано: " + quikErrorDetail(err)
} else {
msg += "; право EXECUTE логину " + login + " выдано"
}
}
log.Printf("depo-procs: %s", msg)
return msg, nil
}
// ---- хендлеры ----
// checkFancyDepoProcs — POST /admin/setup/fancy/depo-procs/check: проверка возможностей (без изменений).
func (h *setupHandlers) checkFancyDepoProcs(w http.ResponseWriter, r *http.Request) {
if !requireDepo(w, r) {
return
}
if r.Method != http.MethodPost {
http.Error(w, "method", http.StatusMethodNotAllowed)
return
}
s := h.rc.Snapshot().Fancy
if s.Server == "" {
setupFlash(w, r, "Процедуры DEPO: сначала сохраните подключение к серверу БД")
return
}
ctx, cancel := context.WithTimeout(depoLoginForce(r.Context()), time.Minute)
defer cancel()
report, canApply, err := depoProcsPreflight(ctx, s, r.FormValue("admin_user"), r.FormValue("admin_password"))
if err != nil {
setupFlash(w, r, "Процедуры DEPO: проверка не удалась — "+err.Error())
return
}
verdict := "можно устанавливать"
if !canApply {
verdict = "установка невозможна"
}
setupFlash(w, r, "Процедуры DEPO ("+verdict+"): "+report)
}
// installFancyDepoProcs — POST /admin/setup/fancy/depo-procs/install: установить процедуры.
func (h *setupHandlers) installFancyDepoProcs(w http.ResponseWriter, r *http.Request) {
if !requireDepo(w, r) {
return
}
if r.Method != http.MethodPost {
http.Error(w, "method", http.StatusMethodNotAllowed)
return
}
s := h.rc.Snapshot().Fancy
if s.Server == "" {
setupFlash(w, r, "Процедуры DEPO: сначала сохраните подключение к серверу БД")
return
}
ctx, cancel := context.WithTimeout(depoLoginForce(r.Context()), 2*time.Minute)
defer cancel()
msg, err := depoProcsInstall(ctx, s, r.FormValue("admin_user"), r.FormValue("admin_password"))
if err != nil {
log.Printf("depo-procs: установка не удалась: %v", err)
setupFlash(w, r, "Процедуры DEPO: не установлены — "+err.Error())
return
}
pmsg, pok := depoProcCheck(ctx, h.rc)
s = h.rc.Snapshot().Fancy
s.DepoProcCheck, s.DepoProcCheckOK, s.DepoProcCheckAt = pmsg, pok, time.Now()
_ = h.rc.UpdateFancy(s)
setupFlash(w, r, "Процедуры DEPO: "+msg+". Проверка обработки: "+pmsg)
}
// fancyDepoProcsScript — GET /admin/setup/fancy/depo-procs/script.sql: единый скрипт для администратора.
func (h *setupHandlers) fancyDepoProcsScript(w http.ResponseWriter, r *http.Request) {
s := h.rc.Snapshot().Fancy
w.Header().Set("Content-Type", "application/sql; charset=utf-8")
w.Header().Set("Content-Disposition", `attachment; filename="bj-depo-procs-mssql.sql"`)
// UTF-8 BOM (0xEF 0xBB 0xBF): SSMS без него читает кириллицу в комментариях как ANSI.
_, _ = w.Write(append([]byte{0xEF, 0xBB, 0xBF}, depoProcsCombined(s)...))
}
@@ -0,0 +1,71 @@
CREATE OR ALTER procedure [dbo].[FTV_M2M_JOB]
as
set nocount on;
declare
@ID int,
@STATE varchar(64);
declare
@ERROR_NUMBER int,
@COMMENT varchar(MAX);
--Новые поручения
declare cur cursor local fast_forward read_only for select ID from FTV_M2M M where M.STATE = 'TODO' order by M.TimeNew;
open cur;
while 0 = 0 begin fetch next from cur into @ID;
if @@FETCH_STATUS <> 0 break;
--Тут можно отправить уведомление, создать входящее сообщение и т.д.
update FTV_M2M set TimeBeg = GETDATE() where ID = @ID;
--Главный процесс
begin try
exec FTV_M2M_PROCESS_CREATE @ID;
set @STATE = 'SUCCESS';
end try
begin catch
set @ERROR_NUMBER = ERROR_NUMBER();
set @COMMENT = ERROR_MESSAGE() + ' // ' + ERROR_PROCEDURE() + ' // ' + trim(str(ERROR_LINE()));
set @STATE = 'ERROR'
end catch
--Тут можно уведомить об ошибочном статусе
update FTV_M2M set STATE = @STATE, TimeEnd = GETDATE(), COMMENT = @COMMENT where ID = @ID;
end;
close cur;
deallocate cur;
--Новые события
declare cur cursor local fast_forward read_only for select ID from FTV_M2M_EVENTS ME where ME.STATE = 'TODO' order by ME.TimeNew;
open cur;
while 0 = 0 begin fetch next from cur into @ID;
if @@FETCH_STATUS <> 0 break;
--Тут можно отправить уведомление, создать входящее сообщение и т.д.
update FTV_M2M_EVENTS set TimeBeg = GETDATE() where ID = @ID;
--Процесс изменения событий
begin try
exec FTV_M2M_PROCESS_EVENTS @ID;
set @STATE = 'SUCCESS';
end try
begin catch
set @ERROR_NUMBER = ERROR_NUMBER();
set @COMMENT = ERROR_MESSAGE() + ' // ' + ERROR_PROCEDURE() + ' // ' + trim(str(ERROR_LINE()));
set @STATE = 'ERROR'
end catch
--Тут можно уведомить об ошибочном статусе
update FTV_M2M_EVENTS set STATE = @STATE, TimeEnd = GETDATE(), COMMENT = @COMMENT where ID = @ID;
end;
close cur;
deallocate cur;
/* тестовый блок удалён при переносе в репозиторий */
@@ -0,0 +1,188 @@
CREATE OR ALTER procedure [dbo].[FTV_M2M_PROCESS_CREATE] (@ID int)
/*
TODO:
Дата поручения - сейчас getdate, но можно передавать, для этого ввести пновое поле в FTV_M2M, или потом брать з события и изменять
Для входящий переводов из СПБ возможно следует дать raiserror
*/
as
set nocount on;
if user = 'dbo' execute as user = 'SYSDBA';
print 'FTV_M2M_PROCESS_CREATE';
print @ID;
--Создаем M2M-поручение вместе с подвалом документа
declare
@WHOS int = 383118,
@GUID uniqueidentifier,
--шапка
@DIRECTION varchar(64),
@CONTRAGENT_M2M_CODE varchar(255),
@DEPONENT_NAME varchar(255),
@DEPONENT_DOC varchar(255),
--подвал
@REFERENCE varchar(36),
@ISIN varchar(12),
@SEC_CODE varchar(255),
@AMOUNT numeric(25, 7),
@PLACE_NUMBER varchar(255),
@LINK_NUMBER varchar(128),
@DEPO_NUMBER varchar(128),
--переменные документа
@D_CAT int,
@NUM varchar(64),
@D_DATE datetime,
@CONTRAGENT int,
@DEPONENT int,
@SHARE int,
@PLACE int,
@DEPO int,
@CLISER_ID int,
@FILER_ID int,
@FILER_NUM varchar(64),
@FILER_DATE datetime,
@DOL_ID int;
select
@GUID = GUID,
@DIRECTION = DIRECTION,
@CONTRAGENT_M2M_CODE = CONTRAGENT_M2M_CODE,
@DEPONENT_NAME = DEPONENT_NAME,
@DEPONENT_DOC = DEPONENT_DOC
from FTV_M2M where ID = @ID;
--1. Критические ошибки
--1.1. Неуникальный GUID
if exists (select top 1 1 from D_CLISER_M2M where DOC_GUID = @GUID)
raiserror ('Неуникальный GUID', 16, 1);
--1.2. Направление перевода
if @DIRECTION = 'IN'
select @D_CAT = ID from OD_DOC_CATS where SYS_NAME = 'I_CLISER_M2M_ZACH'
else if @DIRECTION = 'OUT'
select @D_CAT = ID from OD_DOC_CATS where SYS_NAME = 'I_CLISER_M2M_SPIS'
else
raiserror ('Ошибочное направление DIRECTION (должно быть IN, OUT)', 16, 1);
--1.3. Контрагент
select @CONTRAGENT = C.FACE
from OD_FACE_CODERS C
left join OD_FACES F on F.SELF_ID = C.CODER and F.LAST_FLAG = 1
where F.MNEM = 'M2M' and C.CODE = @CONTRAGENT_M2M_CODE;
if @CONTRAGENT is null
raiserror ('Не найден депозитарий-контрагент', 16, 1);
--1.4. Депонент
select @DEPONENT = F.SELF_ID
from OD_FACES F
where F.LAST_FLAG = 1
and replace(F.NAME, ' ', '') = replace(@DEPONENT_NAME, ' ', '')
and replace(F.DOC_SER + F.DOC_NUM, ' ', '') = replace(@DEPONENT_DOC, ' ', '')
--1.5. Шапка документа
exec GEN_ID 'OD_DOC_ID_GEN', 1, @CLISER_ID out;
--TODO!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
set @D_DATE = getdate();
exec FU_NEXT_DOC_NUM @D_CAT, @D_DATE, null, @CLISER_ID, @NUM out, null;
insert into OD_DOCS
(ID, NUM, PARAM, D_CAT, D_DATE, STATE, DIRECTION, WHOS, KVIT)
select
@CLISER_ID, @NUM, null, @D_CAT, @D_DATE, 0, 0, @WHOS, null;
insert into D_CLISER_M2M
(DOC, DEPONENT, DOC_GUID, DOC_STATUS, IIS_TYPE, IIS_INN, IIS_NUM, IIS_DATE)
select
@CLISER_ID, @DEPONENT, @GUID, 0, 0, null, null, null;
if @DIRECTION = 'IN'
insert into OD_IN_DOCS (DOC, FROM_FACE, PASS_KIND) values (@CLISER_ID, @CONTRAGENT, 8);
else if @DIRECTION = 'OUT'
insert into OD_OUT_DOCS (DOC, TO_FACE, PASS_KIND) values (@CLISER_ID, @CONTRAGENT, 8);
--1.6. Обогащаем FTV_M2M
update FTV_M2M set
DOC = @CLISER_ID,
CONTRAGENT = @CONTRAGENT,
DEPONENT = @DEPONENT
where GUID = @GUID;
--1.7. Подвал документа (только в курсоре)
declare cur cursor local fast_forward read_only for
select ML.REFERENCE, ML.ISIN, ML.SEC_CODE, ML.AMOUNT, ML.PLACE_NUMBER, ML.LINK_NUMBER, ML.DEPO_NUMBER
from FTV_M2M_LINES ML
join FTV_M2M M on M.GUID = ML.GUID
where ML.GUID = @GUID and ML.STATE = 'TODO'
order by ML.ID;
open cur;
while 0 = 0 begin fetch next from cur into @REFERENCE, @ISIN, @SEC_CODE, @AMOUNT, @PLACE_NUMBER, @LINK_NUMBER, @DEPO_NUMBER;
if @@FETCH_STATUS <> 0 break;
--Бумага
select @SHARE = V.ID
from OD_VALUES V
left join OD_VALUE_CODERS C on C.SHARE = V.ID
left join OD_FACES F on F.SELF_ID = C.CODER and F.LAST_FLAG = 1
where V.ISIN = @ISIN and F.MNEM = 'НДЦ' and C.CODE = @SEC_CODE;
--Счет МХ
select @PLACE = C.ID
from OD_CLIENTS C
left join OD_CLIENTS_REC CR on CR.CLIENT = C.SELF_ID and CR.E_DATE = '9999-01-01'
where C.NUMBER like '%' + @PLACE_NUMBER + '%' and CR.LINK_NUMBER = @LINK_NUMBER;
--Счет клиента
select @DEPO = C.ID
from OD_CLIENTS C
where C.NUMBER = @DEPO_NUMBER;
exec GEN_ID 'OD_DOL_ID_GEN', 1, @DOL_ID out;
insert into OD_DOLS (ID, DOC) values (@DOL_ID, @CLISER_ID);
insert into D_CLISER_M2M_DOL (DOL, SHARE, AMOUNT, DOL_REF, PLACE, CLIENT) values (@DOL_ID, @SHARE, @AMOUNT, @REFERENCE, @PLACE, @DEPO);
--Обогащаем FTV_M2M_LINES
update FTV_M2M_LINES set
STATE = 'SUCCESS',
DOC = @CLISER_ID,
DOL = @DOL_ID,
SHARE = @SHARE,
PLACE = @PLACE,
DEPO = @DEPO
where GUID = @GUID and REFERENCE = @REFERENCE;
end;
close cur;
deallocate cur;
--1.8. Входящая запись только для списания (исходящего перевода OUT)
if @DIRECTION = 'OUT' begin
exec GEN_ID 'OD_DOC_ID_GEN', 1, @FILER_ID out;
--TODO!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
set @FILER_DATE = getdate();
exec FU_NEXT_DOC_NUM @D_CAT, @FILER_DATE, null, @FILER_ID, @FILER_NUM out, null;
insert into OD_DOCS (ID, NUM, D_CAT, D_DATE, STATE, DIRECTION, WHOS)
values (@FILER_ID, @FILER_NUM, 255, @FILER_DATE, 2, 1, @WHOS);
insert into D_FILER (DOC, PASS_KIND, RUBRICA, COMMENT, DOC_NUM, DOC_DATE, CORR_FACE, FUND, ACCOUNT, PLACE, ORI_DATE, CHILD_CAT)
values (@FILER_ID, 7, 173, 'M2M поручение на списание через MOEX-МОСТ', @NUM, cast(@D_DATE as date), @DEPONENT, @DEPONENT, @DEPO, @PLACE, @FILER_DATE, @D_CAT);
--Связываем с М2М-поручением
update OD_DOCS set ID = 0, KVIT = @FILER_ID where ID = @CLISER_ID;
end;
@@ -0,0 +1,277 @@
CREATE OR ALTER procedure [dbo].[FTV_M2M_PROCESS_EVENTS] (@ID int)
as
set nocount on;
/*
Вынести создание операции в отдельную процедуру FTV_M2M_PROCESS_OPERATION (параметры - @GUID и @REFERENCE, но проверку параметров оставить тут). Будем возвращать @OPER_ID
*/
if user = 'dbo' execute as user = 'SYSDBA';
print 'FTV_M2M_PROCESS_EVENTS';
print @ID;
--Обрабатываем событие по M2M-поручению
declare
@WHOS int = 383118,
@GUID uniqueidentifier,
@REFERENCE varchar(255),
@EVENT_TYPE varchar(64),
@EVENT_DATE datetime,
@M2M_REJECT_CODE varchar,
@PARAMETERS varchar(max),
@DIRECTION varchar(64),
--
@CONTRAGENT_FACE int,
@DEPONENT_FACE int,
@SHARE int,
@AMOUNT numeric(27, 7),
@PLACE int,
@CLIENT int,
@FACE int,
@CLISER_ID int,
@DOL_ID int,
--
@D_CAT int,
@NUM varchar(64),
@D_DATE datetime,
@OPER_ID int,
@DD_DATE datetime,
@DD_NUM varchar(64);
-- FTV_M2M_EVENTS.REFERENCE может быть пустой, в этом случае событие относится ко всему М2М-поручению
select
@GUID = ME.GUID,
@REFERENCE = ME.REFERENCE,
@EVENT_TYPE = ME.EVENT_TYPE,
@EVENT_DATE = ME.EVENT_DATE,
@M2M_REJECT_CODE = ME.M2M_REJECT_CODE,
@DIRECTION = M.DIRECTION
from FTV_M2M_EVENTS ME
left join FTV_M2M M on M.GUID = ME.GUID
where ME.ID = @ID;
if @REFERENCE is null begin
--Не все события могут быть применены ко всему М2М-поручению
if not (@EVENT_TYPE in ('BJ_RECEIVED', 'BJ_SENT', 'BJ_ORDER_SENT', 'BJ_ORDER_ACCEPTED', 'BJ_EXECUTED') or @EVENT_TYPE like 'BJ_QUIK_%')
raiserror ('Событие этого типа не может быть указано без REFERENCE', 16, 1);
select
@REFERENCE = ME.REFERENCE,
@CLISER_ID = H.DOC,
@DOL_ID = F.DOL,
@DD_DATE = cast(D.D_DATE as date),
@DD_NUM = D.NUM,
@SHARE = F.SHARE,
@AMOUNT = F.AMOUNT,
@PLACE = F.PLACE,
@CLIENT = F.CLIENT,
@FACE = H.DEPONENT
from FTV_M2M_EVENTS ME
left join FTV_M2M_LINES ML on ML.GUID = ME.GUID and ML.REFERENCE = ME.REFERENCE
left join FTV_M2M M on M.GUID = ME.GUID
left join D_CLISER_M2M_DOL F on F.DOL_REF = ME.REFERENCE
left join D_CLISER_M2M H on H.DOC_GUID = ME.GUID
left join OD_DOCS D on D.ID = H.DOC
where ME.ID = @ID;
end
else begin
select
@REFERENCE = ME.REFERENCE,
@CLISER_ID = H.DOC,
@DOL_ID = F.DOL,
@DD_DATE = cast(D.D_DATE as date),
@DD_NUM = D.NUM,
@SHARE = F.SHARE,
@AMOUNT = F.AMOUNT,
@PLACE = F.PLACE,
@CLIENT = F.CLIENT,
@FACE = H.DEPONENT
from FTV_M2M_EVENTS ME
left join FTV_M2M_LINES ML on ML.GUID = ME.GUID and ML.REFERENCE = ME.REFERENCE
left join FTV_M2M M on M.GUID = ME.GUID
left join D_CLISER_M2M_DOL F on F.DOL_REF = ME.REFERENCE
left join D_CLISER_M2M H on H.DOC_GUID = ME.GUID
left join OD_DOCS D on D.ID = H.DOC
where ME.ID = @ID;
--Критические ошибки
if @DOL_ID is null
raiserror ('Не найдена строка M2M-поручение по GUID + REFERENCE', 16, 1);
end;
--Пишем в признаки документа
insert into OD_INVALIDS (DOC, STATUS, COMMENT, DO_ING)
values (
@CLISER_ID,
0,
'Обработано событие: ' + FORMAT(@EVENT_DATE, 'dd.MM.yyyy HH:mm:ss') + ' - ' + @EVENT_TYPE + coalesce (' (референс - ' + @REFERENCE + ')', '') + coalesce (' (парамерты - ' + @PARAMETERS + ')', ''),
'Время начала обработки: ' + FORMAT(GETDATE(), 'dd.MM.yyyy HH:mm:ss')
)
print @EVENT_TYPE;
--ОБРАБОТКА КАЖДОГО ТИПА СОБЫТИЯ
if @EVENT_TYPE = 'BJ_RECEIVED' and @DIRECTION = 'OUT' begin
update OD_DOCS set ID = 0, STATE = 1 where ID = @CLISER_ID; --START - Начало обработки (зарегистрирован)
end
else
if @EVENT_TYPE = 'BJ_SENT' and @DIRECTION = 'IN' begin
update OD_DOCS set ID = 0, STATE = 1 where ID = @CLISER_ID; --START - Начало обработки (зарегистрирован)
end
else
if @EVENT_TYPE = 'BJ_DECISION_REJECT' and @DIRECTION = 'IN' begin
--M2M_STATUS = 10 TODO!!!!!!!!!!!!!!!!!
update D_CLISER_M2M_DOL set M2M_STATUS = 10, M2M_TEXT = '???' where DOL = @DOL_ID;
--только если отказ по всем строкам подвала документа
if not exists (select top 1 DOL from D_CLISER_M2M_DOL F left join OD_DOLS L on L.ID = F.DOL where L.DOC = @CLISER_ID and (M2M_STATUS is null or M2M_TEXT is null)) begin
update OD_DOCS set ID = 0, STATE = 3 where ID = @CLISER_ID; --REJECT - Отмена обработки
end;
end
else
if @EVENT_TYPE = 'BJ_CP_REJECT' and @DIRECTION = 'OUT' begin
--M2M_STATUS = 10 TODO!!!!!!!!!!!!!!!!!
update D_CLISER_M2M_DOL set M2M_STATUS = 10, M2M_TEXT = '???' where DOL = @DOL_ID;
--только если отказ по всем строкам подвала документа
--TODO зачем проверять все строки подвала, если мы не будем делать многострочные исходящие???
if not exists (select top 1 DOL from D_CLISER_M2M_DOL F left join OD_DOLS L on L.ID = F.DOL where L.DOC = @CLISER_ID and (M2M_STATUS is null or M2M_TEXT is null)) begin
update OD_DOCS set ID = 0, STATE = 3 where ID = @CLISER_ID; --REJECT - Отмена обработки
end;
end
else
--TODO что делать если мы проставили статус не по всем строкам документа??
if @EVENT_TYPE = 'BJ_DECISION_CONFIRM' and @DIRECTION = 'IN' begin
update OD_DOCS set ID = 0, STATE = 1 where ID = @CLISER_ID; --START - Начало обработки (зарегистрирован) --ОНО И ТАК УЖЕ ДОЛЖНО БЫТЬ В ЭТОМ СОСТОЯНИИ!!!
end
else
--TODO что делать если контрагент проставил статус не по всем строкам документа??
if @EVENT_TYPE = 'BJ_CP_CONFIRM' and @DIRECTION = 'OUT' begin
update OD_DOCS set ID = 0, STATE = 1 where ID = @CLISER_ID; --START - Начало обработки (зарегистрирован) --ОНО И ТАК УЖЕ ДОЛЖНО БЫТЬ В ЭТОМ СОСТОЯНИИ!!!
end
else
if @EVENT_TYPE = 'BJ_SERVICE_ERROR' or @EVENT_TYPE = 'BJ_CANCELLED' begin
update OD_DOCS set ID = 0, STATE = 3 where ID = @CLISER_ID; --REJECT - Отмена обработки
end
else
if @EVENT_TYPE = 'BJ_ORDER_ACCEPTED' begin
-----TODO exec FTV_M2M_PROCESS_OPERATION @GUID, @REFERENCE, @EVENT_DATE, @OPER_ID out;
--Направление
select @D_CAT = DC.ID
from OD_DOC_CATS DC
where DC.SYS_NAME = case when @DIRECTION = 'IN' then 'OP_INV_ZACH' else 'OP_INV_SPIS' end;
set @D_DATE = @EVENT_DATE;
exec GEN_ID 'OD_DOC_ID_GEN', 1, @OPER_ID out;
exec FU_NEXT_DOC_NUM @D_CAT, @D_DATE, null, @OPER_ID, @NUM out, @D_DATE out;
insert into OD_DOCS (ID, NUM, PARAM, D_CAT, D_DATE, STATE, DIRECTION, WHOS, KVIT)
values (@OPER_ID, @NUM, null, @D_CAT, @D_DATE, 0, 0, @WHOS, @CLISER_ID);
if @DIRECTION = 'IN' begin
--порядок полей - по форме документа (сверху вниз)
insert into U_OP_INV_ZACH
(
DOC,
DD_TYPE,
DD_DATE, DD_NUM,
SHARE, AMOUNT, BLANKS,
PLACE,
D_NUM,
DEPO,
K_NUM,
MARKET,
CONDITION, IS_REG, TAX_R,
B_TYPE,
B_DATE, B_NUM,
WIR_DATE, NOTIFY_DATE, REG_DATE,
IS_BLOCK,
FROM_FACE,
REJECT_REASON,
COMMENT
)
values
(
@OPER_ID,
1 /*Клиентское*/,
@DD_DATE, @DD_NUM,
@SHARE, @AMOUNT, null,
@PLACE,
314, /*98015 - 0 - Ценные бумаги и цифровые активы на хранении в других депозитариях (НОСТРО депо расчетный)*/
@CLIENT,
836, /*98070 - 4 - Ценные бумаги и цифровые активы на торгах*/
8106, /*ММВБ - ПАО Московская Биржа*/
null, null, null,
31, /*Отчет депозитария*/
@EVENT_DATE, 'Вопрос, что тут пишем',
@EVENT_DATE, @EVENT_DATE, @EVENT_DATE, --Берем из BJ
0,
@FACE,
null, --А есди это отмена??
'Перевод M2M (без поручения), GUID ' + cast(@GUID as varchar(36)) + ', референс ' + @REFERENCE
);
end
else
if @DIRECTION = 'OUT' begin
insert into U_OP_INV_SPIS
(
DOC,
DD_TYPE,
DD_DATE, DD_NUM,
SHARE, AMOUNT, BLANKS,
PLACE,
D_NUM,
DEPO,
K_NUM,
MARKET,
OLD_CONDITIONS, IS_REG, TAX_R,
B_TYPE,
B_DATE, B_NUM,
WIR_DATE, NOTIFY_DATE, REG_DATE,
IS_BLOCK,
FROM_FACE,
REJECT_REASON,
COMMENT
)
values
(
@OPER_ID,
1 /*Клиентское*/,
@DD_DATE, @DD_NUM,
@SHARE, @AMOUNT, null,
@PLACE,
314, /*98015 - 0 - Ценные бумаги и цифровые активы на хранении в других депозитариях (НОСТРО депо расчетный)*/
@CLIENT,
836, /*98070 - 4 - Ценные бумаги и цифровые активы на торгах*/
8106, /*ММВБ - ПАО Московская Биржа*/
null, null, null,
31, /*Отчет депозитария*/
@EVENT_DATE, 'Вопрос, что тут пишем',
@EVENT_DATE, @EVENT_DATE, @EVENT_DATE, --Берем из BJ
0,
@FACE,
null, --А есди это отмена??
'Отчет НКО АО НРД [номер, дата]. Перевод M2M, GUID ' + cast(@GUID as varchar(36)) + ', референс ' + @REFERENCE
);
end;
--И сразу проводим
exec FTV_M2M_PROCESS_WIRING @OPER_ID;
end
else if @EVENT_TYPE = 'CREATE_WIRING' begin
print 'CREATE_WIRING'
end;
@@ -0,0 +1,165 @@
CREATE OR ALTER procedure [dbo].[FTV_M2M_PROCESS_OPERATION] @GUID uniqueidentifier, @REFERENCE varchar(255), @OPER_DATE datetime, @OPER_ID int output
/*
Создает операцию, возвращает OPER_ID созданной операции
Параметры нацелены прежде всего на строки уже созданного М2М-поручения D_CLISER_M2M_DOL, а не на буферные таблицы (к ним больше не обращаемся)
Все проверки идут до вызова, но элементарные проверки можно сделать и тут. Или дать raiserror
Мы существенно разгружаем FTV_M2M_PROCESS_EVENTS
declare @OPER_ID int;
exec FTV_M2M_PROCESS_OPERATION @GUID = 'BAE1FA60-EB0A-4FEE-B12C-018D1EAFCBD2', @REFERENCE = 'M2M0917165643657', @OPER_DATE = '20260922', @OPER_ID = @OPER_ID out;
select @OPER_ID;
*/
as
set nocount on;
if user = 'dbo' execute as user = 'SYSDBA';
print 'FTV_M2M_PROCESS_OPERATION' + ' GUID = ' + cast(@GUID as varchar(36)) + ', REFERENCE = ' + @REFERENCE;
declare
@WHOS int = 383118,
@CLISER_ID int,
@CLISER_NUM varchar(64),
@CLISER_DATE datetime,
@DIRECTION varchar(64),
@DD_DATE datetime,
@DD_NUM varchar(64),
@SHARE int,
@AMOUNT numeric(27, 7),
@CLIENT int,
@FACE int,
@PLACE int,
@D_CAT int,
@NUM varchar(64);
select
@CLISER_ID = H.DOC,
@CLISER_NUM = D.NUM,
@CLISER_DATE= D.D_DATE,
@DIRECTION = case
when DC.SYS_NAME = 'I_CLISER_M2M_ZACH' then 'IN'
when DC.SYS_NAME = 'I_CLISER_M2M_SPIS' then 'OUT'
end,
@DD_DATE = cast(D.D_DATE as date),
@DD_NUM = D.NUM,
@SHARE = F.SHARE,
@AMOUNT = F.AMOUNT,
@CLIENT = F.CLIENT,
@FACE = H.DEPONENT,
@PLACE = F.PLACE
from D_CLISER_M2M_DOL F
left join OD_DOLS L on L.ID = F.DOL
left join OD_DOCS D on D.ID = L.DOC
left join D_CLISER_M2M H on H.DOC = D.ID
left join OD_DOC_CATS DC on DC.ID = D.D_CAT
where H.DOC_GUID = @GUID and F.DOL_REF = @REFERENCE;
--Элеметарные проверки
if @CLISER_ID is null
raiserror ('Не найдено M2M-поручение по GUID + REFERENCE', 16, 1);
--Направление
select @D_CAT = DC.ID
from OD_DOC_CATS DC
where DC.SYS_NAME = case when @DIRECTION = 'IN' then 'OP_INV_ZACH' else 'OP_INV_SPIS' end;
--ID и номер операции
exec GEN_ID 'OD_DOC_ID_GEN', 1, @OPER_ID out;
exec FU_NEXT_DOC_NUM @D_CAT, @OPER_DATE, null, @OPER_ID, @NUM out, null;
--Создаем документ
insert into OD_DOCS (ID, NUM, PARAM, D_CAT, D_DATE, STATE, DIRECTION, WHOS, KVIT)
values (@OPER_ID, @NUM, null, @D_CAT, @OPER_DATE, 0, 0, @WHOS, @CLISER_ID);
if @DIRECTION = 'IN' begin
insert into U_OP_INV_ZACH
(
DOC,
DD_TYPE,
DD_DATE, DD_NUM,
SHARE, AMOUNT, BLANKS,
PLACE,
D_NUM,
DEPO,
K_NUM,
MARKET,
CONDITION, IS_REG, TAX_R,
B_TYPE,
B_DATE, B_NUM,
WIR_DATE, NOTIFY_DATE, REG_DATE,
IS_BLOCK,
FROM_FACE,
REJECT_REASON,
COMMENT
)
values
(
@OPER_ID,
1 /*Клиентское*/,
@DD_DATE, @DD_NUM,
@SHARE, @AMOUNT, null,
@PLACE,
314, /*98015 - 0 - Ценные бумаги и цифровые активы на хранении в других депозитариях (НОСТРО депо расчетный)*/
@CLIENT,
836, /*98070 - 4 - Ценные бумаги и цифровые активы на торгах*/
8106, /*ММВБ - ПАО Московская Биржа*/
null, null, null,
31, /*Отчет депозитария*/
@CLISER_DATE, @CLISER_NUM,
@OPER_DATE, @OPER_DATE, @OPER_DATE,
0,
@FACE,
null,
'Перевод M2M (без поручения), GUID ' + cast(@GUID as varchar(36)) + ', референс ' + @REFERENCE
);
end
else
if @DIRECTION = 'OUT' begin
insert into U_OP_INV_SPIS
(
DOC,
DD_TYPE,
DD_DATE, DD_NUM,
SHARE, AMOUNT, BLANKS,
PLACE,
D_NUM,
DEPO,
K_NUM,
MARKET,
OLD_CONDITIONS, IS_REG, TAX_R,
B_TYPE,
B_DATE, B_NUM,
WIR_DATE, NOTIFY_DATE, REG_DATE,
IS_BLOCK,
FROM_FACE,
REJECT_REASON,
COMMENT
)
values
(
@OPER_ID,
1 /*Клиентское*/,
@DD_DATE, @DD_NUM,
@SHARE, @AMOUNT, null,
@PLACE,
314, /*98015 - 0 - Ценные бумаги и цифровые активы на хранении в других депозитариях (НОСТРО депо расчетный)*/
@CLIENT,
836, /*98070 - 4 - Ценные бумаги и цифровые активы на торгах*/
8106, /*ММВБ - ПАО Московская Биржа*/
null, null, null,
31, /*Отчет депозитария*/
@CLISER_DATE, @CLISER_NUM,
@OPER_DATE, @OPER_DATE, @OPER_DATE,
0,
@FACE,
null,
'Отчет НКО АО НРД [номер, дата]. Перевод M2M, GUID ' + cast(@GUID as varchar(36)) + ', референс ' + @REFERENCE
);
end;
--И сразу проводим
exec FTV_M2M_PROCESS_WIRING @OPER_ID;
@@ -0,0 +1,172 @@
CREATE OR ALTER procedure [dbo].[FTV_M2M_PROCESS_WIRING] (@OPER_ID int)
as
set nocount on;
/*
--Выполняет директиву "Обраотать" для документа "Зачисление" или "Списание"
--Все значения переменных берем только из самого документа, М2М-поручения и трансфертные таблицы тут не используются
exec FTV_M2M_PROCESS_WIRING 75069916;
*/
if user = 'dbo' execute as user = 'SYSDBA';
declare @USER int = 84;
declare
@DIRECTION varchar(3),
@D_CAT int,
@SHARE int,
@PLACE int,
@DEPO int,
@AMOUNT numeric(22,7),
@COMMENT varchar(255),
@B_TYPE int,
@B_NUM varchar(255),
@B_DATE datetime,
@WIR_DATE datetime,
@D_NUM int,
@K_NUM int,
@MARKET int,
@NAME varchar(255),
@TEXT_B_TYPE varchar(255),
@WIR_ID int;
declare
@D_REG_1 int, @K_REG_1 int,
@D_REG_2 int, @K_REG_2 int,
@D_REG_3 int, @K_REG_3 int,
@D_REG_4 int, @K_REG_4 int,
@D_REG_5 int, @K_REG_5 int,
@D_REG_6 int, @K_REG_6 int;
declare
@DIR_ID int,
@OUT_STATE int,
@STEP_ID int;
--НАПРАВЛЕНИЕ
if exists (select top 1 DOC from U_OP_INV_ZACH where DOC = @OPER_ID) set @DIRECTION = 'IN';
else
if exists(select top 1 DOC from U_OP_INV_SPIS where DOC = @OPER_ID) set @DIRECTION = 'OUT';
else begin
print 'Не найден документ ID = ' + trim(str(@OPER_ID));
return; --TODO исключительная ситуация
end;
--ПРОВЕРКИ, ЧТО ДОКУМЕНТ МОЖНО ПРОВОДИТЬ
--Состояние документа
--Связь с M2M
--TODO
--ЗНАЧЕНИЯ ПЕРЕМЕННЫХ
if @DIRECTION = 'IN' begin
--Смотрим шапку операции
select
@D_CAT = D.D_CAT,
@D_NUM = H.D_NUM,
@K_NUM = H.K_NUM,
@B_TYPE = H.B_TYPE,
@B_NUM = H.B_NUM,
@B_DATE = H.B_DATE,
@SHARE = H.SHARE,
@AMOUNT = H.AMOUNT,
@PLACE = H.PLACE,
@DEPO = H.DEPO,
@MARKET = H.MARKET,
@WIR_DATE = H.WIR_DATE,
@NAME = 'Зачисление'
from OD_DOCS D
left join U_OP_INV_ZACH H ON H.DOC = D.ID
where D.ID = @OPER_ID;
--D_REG
select
@D_REG_1 = C.SELF_ID,
@D_REG_2 = C.FACE,
@D_REG_3 = 0,
@D_REG_4 = 0,
@D_REG_5 = 0,
@D_REG_6 = @PLACE
from OD_CLIENTS C where C.ID = @PLACE;
--K_REG
select
@K_REG_1 = C.SELF_ID,
@K_REG_2 = @MARKET,
@K_REG_3 = 0,
@K_REG_4 = @D_REG_1,
@K_REG_5 = @PLACE,
@K_REG_6 = @DEPO
from OD_CLIENTS C where C.ID = @DEPO;
end
else if @DIRECTION = 'OUT' begin
--Смотрим шапку операции
select
@D_CAT = D.D_CAT,
@D_NUM = H.K_NUM, --реверс
@K_NUM = H.D_NUM, --реверс
@B_TYPE = H.B_TYPE,
@B_NUM = H.B_NUM,
@B_DATE = H.B_DATE,
@SHARE = H.SHARE,
@AMOUNT = H.AMOUNT,
@PLACE = H.PLACE,
@DEPO = H.DEPO,
@MARKET = H.MARKET,
@WIR_DATE = H.WIR_DATE,
@NAME = 'Списание'
from OD_DOCS D
left join U_OP_INV_SPIS H ON H.DOC = D.ID
where D.ID = @OPER_ID;
--K_REG
select
@K_REG_1 = C.SELF_ID,
@K_REG_2 = C.FACE,
@K_REG_3 = 0,
@K_REG_4 = 0,
@K_REG_5 = 0,
@K_REG_6 = @PLACE
from OD_CLIENTS C where C.ID = @PLACE;
--D_REG
select
@D_REG_1 = C.SELF_ID,
@D_REG_2 = @MARKET,
@D_REG_3 = 0,
@D_REG_4 = @K_REG_1,
@D_REG_5 = @PLACE,
@D_REG_6 = @DEPO
from OD_CLIENTS C where C.ID = @DEPO;
end;
--ДОПОЛНИТЕЛЬНЫЕ ПАРАМЕТРЫ
select @TEXT_B_TYPE = NAME from OD_SYS_TABS where CODE = 'DEPO96_DCLA' and NUM = @B_TYPE;
set @COMMENT = FORMATMESSAGE('%s; %s N%s от %s', @COMMENT, @TEXT_B_TYPE, @B_NUM, convert(varchar, @B_DATE, 104));
--ДИРЕКТИВА
select @DIR_ID = ID, @OUT_STATE = OUT_STATE from OD_STEP_TYPES where SYS_NAME = 'PROCESS' and D_CAT = @D_CAT;
exec GEN_ID 'OD_STEPS_ID_GEN', 1, @STEP_ID out;
insert into OD_STEPS (ID, DOC, S_TYPE, S_DATE, ERR_CODE, OUT_STATE, OPER) values (@STEP_ID, @OPER_ID, @DIR_ID, GETDATE(), 0, @OUT_STATE, @USER);
--update OD_STEPS set OPER = @USER where ID = @STEP_ID; может и не пригодится
--ПРОВОДКА
exec OD_PR_INS_WIRS
null, 4, @WIR_DATE, @NAME, @COMMENT, @AMOUNT, @AMOUNT, null, 0, 0, null, @STEP_ID, null, @USER, 'F',
@D_NUM, @SHARE, @D_REG_1, @D_REG_2, @D_REG_3, @D_REG_4, @D_REG_5, @D_REG_6, --дебет
@K_NUM, @SHARE, @K_REG_1, @K_REG_2, @K_REG_3, @K_REG_4, @K_REG_5, @K_REG_6, --кредит
@WIR_ID out, null, null, null; --return
--СОСТОЯНИЕ ДОКУМЕНТА
update OD_DOCS set STATE = @OUT_STATE where ID = @OPER_ID;
--select @WIR_ID;
--select * from OD_WIRING where ID = @WIR_ID;
+3
View File
@@ -26,6 +26,9 @@ func (h *setupHandlers) registerFancy(mux *http.ServeMux) {
mux.HandleFunc("/admin/setup/fancy/depo-tables/script.sql", h.fancyDepoTablesScript)
mux.HandleFunc("/admin/setup/fancy/depo-login/script.sql", h.fancyDepoLoginScript)
mux.HandleFunc("/admin/setup/fancy/depo-job/script.sql", h.fancyDepoJobScript)
mux.HandleFunc("/admin/setup/fancy/depo-procs/check", h.checkFancyDepoProcs)
mux.HandleFunc("/admin/setup/fancy/depo-procs/install", h.installFancyDepoProcs)
mux.HandleFunc("/admin/setup/fancy/depo-procs/script.sql", h.fancyDepoProcsScript)
mux.HandleFunc("/admin/fancy-queue", h.fancyQueue)
mux.HandleFunc("/admin/fancy-queue/requeue", h.fancyQueueRequeue)
mux.HandleFunc("/admin/fancy-queue/dismiss", h.fancyQueueDismiss)
@@ -113,6 +113,25 @@
{{if and (eq .Settings.Fancy.DepoJobMode "bj") .Settings.Fancy.DepoJobLast}}<p class="muted" style="font-size:12px;margin-top:6px">Последний вызов процедуры — {{.Settings.Fancy.DepoJobLast}}</p>{{end}}
{{end}}
</div>
<div class="card" id="sec-fancy-depo-procs">
<h2><span class="dot {{if .Settings.Fancy.DepoProcCheckOK}}ok{{else}}warn{{end}}"></span>Процедуры обработки bj2depo</h2>
<p class="muted">Процедуры <code>FTV_M2M_PROCESS_*</code> и <code>FTV_M2M_JOB</code> встроены в bj. Сначала «Проверить возможности» (ничего не меняет), затем «Установить» — bj выполнит их в базе DEPO в порядке WIRING&rarr;OPERATION&rarr;EVENTS&rarr;CREATE&rarr;JOB и выдаст логину bj право запуска.</p>
<form method="post" style="display:grid;gap:10px;max-width:720px">
<input type="hidden" name="section" value="sec-fancy-depo-procs">
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:10px">
<label>Логин администратора SQL <input type="text" name="admin_user" autocomplete="off" placeholder="sa" style="width:100%"></label>
<label>Пароль <input type="password" name="admin_password" autocomplete="new-password" style="width:100%"></label>
</div>
<p class="muted" style="font-size:12px;margin:0">Логин sysadmin (например sa) вводится один раз и не сохраняется. Пусто — bj попробует своим логином (прав на создание процедур у него обычно нет).</p>
<div style="display:flex;gap:10px;flex-wrap:wrap">
<button type="submit" class="btn" formaction="/admin/setup/fancy/depo-procs/check">Проверить возможности</button>
<button type="submit" class="btn btn-ok" formaction="/admin/setup/fancy/depo-procs/install" onclick="return confirm('Установить процедуры обработки в базу DEPO {{.Settings.Fancy.DepoDB}}?')">⚙ Установить процедуры</button>
</div>
</form>
<p style="margin-top:8px"><a href="/admin/setup/fancy/depo-procs/script.sql">Скачать скрипт bj-depo-procs-mssql.sql</a> <span class="muted" style="font-size:12px">— для ручной установки администратором в SSMS</span></p>
{{if .Settings.Fancy.DepoProcCheck}}<p style="margin-top:6px;font-size:13px;{{if .Settings.Fancy.DepoProcCheckOK}}color:var(--ok){{else}}color:var(--warn){{end}}">{{if .Settings.Fancy.DepoProcCheckOK}}✓{{else}}⚠{{end}} {{.Settings.Fancy.DepoProcCheck}}</p>{{end}}
</div>
{{end}}
{{define "verline"}}{{if .Title}}<p class="muted" style="font-size:12.5px;margin:-6px 0 12px">{{.Title}}: {{if .Version}}<strong>{{.Version}}</strong>{{else}}не определена{{end}}{{with .Detail}} · {{.}}{{end}}{{with .Err}} · <span style="color:var(--err)">{{.}}</span>{{end}}</p>{{end}}{{end}}