DEPO на Firebird в мастере: подготовка базы одним действием, как для MSSQL
Мастер, шаг «Учёт DEPO», и «Обмен с DEPO» в настройках: СУБД «Firebird» (Fancy на Firebird 2.5). - Подключение: драйвер nakagami/firebirdsql, путь к файлу базы или псевдоним, WIN1251. Запросы bj к DEPO (T-SQL) подключение переводит само (fancyfb.go): механически (dbo., TOP, ISNULL, GETDATE, LEN, N, exec) вне строк и комментариев, точные тексты — fsql/fb.go (каталоги, LIKE [_], COUNT(*) OVER, SELECT без FROM, PATINDEX); @имена — позиционные ?, GUID — в верхнем регистре. Системные запросы из кода — в fsql/depocatalog.go. - «Подготовить» логином SYSDBA (depo_setup_fb.go): проверка базы и UDF Fancy, логин SYSDBA_BJ (Fancy даёт права на документы логинам SYSDBA%; для Firebird — обычный пользователь), оператор-робот Fancy, кодировщик M2M, привязки FTV_M2M_SETTINGS по признакам Fancy, таблицы, процедуры, права; обработку запускает bj. «Получить скрипт» — то же для isql в WIN1251; письмо администратору, установка процедур, плашка обновления. - Скрипты Firebird встроены (depoprocs/firebird, тест сверяет с deploy/); количества — DOUBLE PRECISION: из диалекта 3 в базу диалекта 1 процедуру с NUMERIC(18,7) не создать. - Проверено на копии базы Fancy (отдельный экземпляр bj): мастер с нуля — все проверки, BJ_PING; тестовый режим DEPO — К1 Бубликов, зачисление и списание до исполнения без ошибок; очередь, справочник НРД. Тест FBLive готовит на базе все запросы bj к DEPO: 94 из 101, оставшиеся 7 — ЛК и ручной ввод (следующий этап). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn
This commit is contained in:
@@ -126,6 +126,10 @@ var depoSchemaFixes = []struct{ Name, SQL string }{
|
||||
// depoProcsHash — отпечаток набора процедур, который bj установит (с учётом правки под клиента).
|
||||
func depoProcsHash(f FancyMSSQLSettings) string {
|
||||
h := sha256.New()
|
||||
if f.IsFirebird() { // таблицы, привязки, процедуры и права Firebird (depo_setup_fb.go)
|
||||
h.Write(depoFBHash())
|
||||
return hex.EncodeToString(h.Sum(nil))[:16]
|
||||
}
|
||||
for _, fx := range depoSchemaFixes {
|
||||
h.Write([]byte(fx.Name + "\n" + fx.SQL + "\n"))
|
||||
}
|
||||
@@ -154,6 +158,9 @@ func depoProcList() []struct{ Name, File string } { return depoProcInstallOrder
|
||||
|
||||
// depoProcsCombined — единый скрипт установки (для скачивания администратору): USE, процедуры, GRANT.
|
||||
func depoProcsCombined(f FancyMSSQLSettings) string {
|
||||
if f.IsFirebird() {
|
||||
return depoSetupScriptFB(f, f.DepoDB(), depoSetupLogin(f), "")
|
||||
}
|
||||
return depoProcsScriptFor(f, f.DepoDB(), depoSetupLogin(f))
|
||||
}
|
||||
|
||||
@@ -233,7 +240,10 @@ func quoteIdent(s string) string { return "[" + strings.ReplaceAll(s, "]", "]]")
|
||||
// для экрана, признак «можно применять» и ошибку подключения/проверки.
|
||||
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")
|
||||
return "", false, fmt.Errorf("установка процедур bj2depo поддержана только для MSSQL и Firebird")
|
||||
}
|
||||
if f.IsFirebird() {
|
||||
return depoProcsPreflightFB(ctx, f, adminUser, adminPass)
|
||||
}
|
||||
conn := f
|
||||
admin := strings.TrimSpace(adminUser)
|
||||
@@ -314,6 +324,9 @@ func depoProcErrHint(msg string) string {
|
||||
// depoProcsInstall — установить процедуры логином администратора. Сначала preflight; при отсутствии
|
||||
// прав — отказ без изменений. Возвращает итоговое сообщение.
|
||||
func depoProcsInstall(ctx context.Context, f FancyMSSQLSettings, adminUser, adminPass string) (string, error) {
|
||||
if f.IsFirebird() {
|
||||
return depoProcsInstallFB(ctx, f, adminUser, adminPass)
|
||||
}
|
||||
report, canApply, err := depoProcsPreflight(ctx, f, adminUser, adminPass)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -401,6 +414,12 @@ func (h *setupHandlers) installFancyDepoProcs(w http.ResponseWriter, r *http.Req
|
||||
// fancyDepoProcsScript — GET /admin/setup/fancy/depo-procs/script.sql: единый скрипт для администратора.
|
||||
func (h *setupHandlers) fancyDepoProcsScript(w http.ResponseWriter, r *http.Request) {
|
||||
s := h.rc.Snapshot().Fancy
|
||||
if s.IsFirebird() { // isql -ch WIN1251
|
||||
w.Header().Set("Content-Type", "application/sql; charset=windows-1251")
|
||||
w.Header().Set("Content-Disposition", `attachment; filename="bj-depo-procs-firebird.sql"`)
|
||||
_, _ = w.Write(win1251(depoProcsCombined(s)))
|
||||
return
|
||||
}
|
||||
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.
|
||||
|
||||
@@ -20,8 +20,14 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// depoSetupLogin — логин bj в DEPO (если в настройках уже есть допустимый — он).
|
||||
// depoSetupLogin — логин bj в DEPO (если в настройках уже есть допустимый — он). Firebird — SYSDBA_BJ.
|
||||
func depoSetupLogin(f FancyMSSQLSettings) string {
|
||||
if f.IsFirebird() {
|
||||
if u := strings.TrimSpace(f.User); fbLoginRe.MatchString(u) {
|
||||
return strings.ToUpper(u)
|
||||
}
|
||||
return depoFBDefaultLogin
|
||||
}
|
||||
if u := strings.TrimSpace(f.User); u != "" && depoLoginRe.MatchString(u) {
|
||||
return u
|
||||
}
|
||||
@@ -56,6 +62,9 @@ func depoSetupScript(f FancyMSSQLSettings, db, login, pass string, agentJob bool
|
||||
if f.IsPostgres() {
|
||||
return depoSetupScriptPG(f, db, login, pass)
|
||||
}
|
||||
if f.IsFirebird() {
|
||||
return depoSetupScriptFB(f, db, login, pass)
|
||||
}
|
||||
var b strings.Builder
|
||||
job := "вызывает сам bj"
|
||||
if agentJob {
|
||||
@@ -217,6 +226,9 @@ func depoSetupFileName(f FancyMSSQLSettings) string {
|
||||
if f.IsPostgres() {
|
||||
return "bj-depo-setup-postgres.sql"
|
||||
}
|
||||
if f.IsFirebird() {
|
||||
return "bj-depo-setup-firebird.sql"
|
||||
}
|
||||
return "bj-depo-setup-mssql.sql"
|
||||
}
|
||||
|
||||
@@ -228,6 +240,12 @@ type depoStep func(key, state, text string)
|
||||
func depoSetupRun(ctx context.Context, f FancyMSSQLSettings, db, adminUser, adminPass, login, pass string, agentJob bool, step depoStep) error {
|
||||
adm := f
|
||||
adm.User, adm.Password = strings.TrimSpace(adminUser), adminPass
|
||||
if f.IsFirebird() {
|
||||
if !validFBPath(db) {
|
||||
return fmt.Errorf("укажите путь к базе DEPO на сервере Firebird")
|
||||
}
|
||||
return depoFBSetup(ctx, adm, db, login, pass, step)
|
||||
}
|
||||
if !validDBName(db) {
|
||||
return fmt.Errorf("укажите имя базы DEPO")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,464 @@
|
||||
package lkgateway
|
||||
|
||||
// Подготовка базы DEPO на Firebird (Fancy на Firebird 2.5, 27.10.2026) — то же, что depo_setup.go для MSSQL:
|
||||
// одним действием логином администратора (обычно SYSDBA, не сохраняется) или скриптом для администратора.
|
||||
// Отличия от MSSQL:
|
||||
// - логин bj — SYSDBA_BJ: Fancy даёт логинам SYSDBA% все права на документы (FU_HAVE_RIGHT: USER like 'SYSDBA%'),
|
||||
// а EXECUTE AS, которым MSSQL-версия переходит на SYSDBA, в Firebird нет. Для Firebird это обычный
|
||||
// пользователь сервера: права только выданные — чтение учёта, таблицы обмена, запуск FTV_M2M_JOB;
|
||||
// - логин bj заводится оператором Fancy (OD_OPERATORS, признак «робот»): по оператору текущего пользователя
|
||||
// Fancy выбирает депозитарий и нумерует документы (FU_NEXT_DOC_NUM);
|
||||
// - обработку запускает bj (SQL Server Agent нет);
|
||||
// - таблицы, процедуры и права — встроенные скрипты depoprocs/firebird (копия — deploy/depo/fancy-m2m-firebird);
|
||||
// привязки к справочникам клиента (FTV_M2M_SETTINGS) bj определяет сам по признакам Fancy.
|
||||
// Пароль Firebird 2.5 различает только по первым 8 знакам.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"embed"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/text/encoding"
|
||||
"golang.org/x/text/encoding/charmap"
|
||||
)
|
||||
|
||||
//go:embed depoprocs/firebird/*.sql
|
||||
var depoFBFS embed.FS
|
||||
|
||||
// depoFBScriptFiles — скрипты установки по порядку: таблицы, процедуры, права.
|
||||
var depoFBScriptFiles = []string{"00_FTV_M2M_TABLES.sql", "10_FTV_M2M_PROCS.sql", "20_FTV_M2M_GRANTS.sql"}
|
||||
|
||||
// depoFBDefaultLogin — логин bj в Firebird (имя на SYSDBA — условие прав Fancy).
|
||||
const depoFBDefaultLogin = "SYSDBA_BJ"
|
||||
|
||||
// fbLoginRe — имя пользователя Firebird без кавычек, на SYSDBA (иначе Fancy запретит bj создавать документы).
|
||||
var fbLoginRe = regexp.MustCompile(`^(?i:SYSDBA)[A-Za-z0-9_$]{1,25}$`)
|
||||
|
||||
func depoFBScript(name string) string {
|
||||
b, _ := depoFBFS.ReadFile("depoprocs/firebird/" + name)
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// depoFBGrants — права (20) для логина bj.
|
||||
func depoFBGrants(login string) string {
|
||||
return strings.ReplaceAll(depoFBScript("20_FTV_M2M_GRANTS.sql"), ":BJ_USER", login)
|
||||
}
|
||||
|
||||
// depoFBBindings — оператор Fancy для логина bj, кодировщик M2M и привязки FTV_M2M_SETTINGS по признакам Fancy
|
||||
// (заполняются только пустые: исправленное вручную повторная установка не трогает). Один блок PSQL — и для
|
||||
// установки из bj, и в скрипте администратора.
|
||||
func depoFBBindings(login string) string {
|
||||
return fmt.Sprintf(`EXECUTE BLOCK AS
|
||||
DECLARE VARIABLE V INTEGER;
|
||||
BEGIN
|
||||
/* логин bj — оператор Fancy (робот): по оператору Fancy выбирает депозитарий и нумерует документы */
|
||||
IF (NOT EXISTS (SELECT 1 FROM OD_OPERATORS WHERE USER_ID = '%[1]s')) THEN
|
||||
INSERT INTO OD_OPERATORS (ID, USER_ID, NAME, U_FACE, PLAN_ID, USER_DOL, IS_ROBOT)
|
||||
SELECT FIRST 1 GEN_ID(OD_OPERATORS_ID_GEN, 1), '%[1]s', 'Bridge&Join (переводы M2M)', O.U_FACE, O.PLAN_ID, 'Робот M2M', 1
|
||||
FROM OD_OPERATORS O WHERE O.U_FACE IN (SELECT S.WHOS FROM OD_SETS S)
|
||||
ORDER BY CASE WHEN O.USER_ID = 'SYSDBA' THEN 0 ELSE 1 END, O.ID;
|
||||
/* кодировщик M2M — коды участников сервиса из справочника НРД (событие HANDBOOK) */
|
||||
IF (NOT EXISTS (SELECT 1 FROM OD_FACES WHERE LAST_FLAG = 1 AND MNEM = 'M2M')) THEN
|
||||
INSERT INTO OD_FACES (ID, S_TYPE, NAME, MNEM) VALUES (GEN_ID(OD_FACES_ID_GEN, 1), 1, 'Кодировщик M2M (справочник участников НРД)', 'M2M');
|
||||
|
||||
V = NULL; SELECT FIRST 1 U_FACE FROM OD_OPERATORS WHERE USER_ID = '%[1]s' INTO :V;
|
||||
UPDATE FTV_M2M_SETTINGS SET VAL = :V WHERE NAME = 'WHOS' AND VAL IS NULL;
|
||||
V = NULL; SELECT FIRST 1 ID FROM OD_OPERATORS WHERE USER_ID = '%[1]s' INTO :V;
|
||||
UPDATE FTV_M2M_SETTINGS SET VAL = :V WHERE NAME = 'OPER_USER' AND VAL IS NULL;
|
||||
V = NULL; SELECT FIRST 1 ID FROM OD_DOC_CATS WHERE SYS_NAME = 'D_FILER' ORDER BY ID INTO :V;
|
||||
UPDATE FTV_M2M_SETTINGS SET VAL = :V WHERE NAME = 'FILER_CAT' AND VAL IS NULL;
|
||||
V = NULL; SELECT FIRST 1 ID FROM OD_BALANCES WHERE NUM_S = '98010' ORDER BY ID INTO :V;
|
||||
UPDATE FTV_M2M_SETTINGS SET VAL = :V WHERE NAME = 'D_NUM' AND VAL IS NULL;
|
||||
V = NULL; SELECT FIRST 1 ID FROM OD_BALANCES WHERE NUM_S = '98070.04' ORDER BY ID INTO :V;
|
||||
UPDATE FTV_M2M_SETTINGS SET VAL = :V WHERE NAME = 'K_NUM' AND VAL IS NULL;
|
||||
V = NULL; SELECT FIRST 1 SELF_ID FROM OD_FACES WHERE LAST_FLAG = 1 AND (MNEM = 'ММВБ' OR INN = '7702077840') ORDER BY CASE WHEN MNEM = 'ММВБ' THEN 0 ELSE 1 END, ID INTO :V;
|
||||
UPDATE FTV_M2M_SETTINGS SET VAL = :V WHERE NAME = 'MARKET' AND VAL IS NULL;
|
||||
V = NULL; SELECT FIRST 1 NUM FROM OD_SYS_TABS WHERE CODE = 'DEPO96_DCLA' AND NAME IN ('Отчет депозитария', 'Отчёт депозитария') INTO :V;
|
||||
UPDATE FTV_M2M_SETTINGS SET VAL = :V WHERE NAME = 'B_TYPE' AND VAL IS NULL;
|
||||
V = NULL; SELECT FIRST 1 SELF_ID FROM OD_FACES WHERE LAST_FLAG = 1 AND (MNEM = 'НДЦ' OR INN = '7702165310') ORDER BY CASE WHEN MNEM = 'НДЦ' THEN 0 ELSE 1 END, ID INTO :V;
|
||||
UPDATE FTV_M2M_SETTINGS SET VAL = :V WHERE NAME = 'CORR_FACE' AND VAL IS NULL;
|
||||
V = NULL; SELECT FIRST 1 RUBRICA FROM D_FILER WHERE RUBRICA IS NOT NULL GROUP BY RUBRICA ORDER BY COUNT(*) DESC INTO :V;
|
||||
UPDATE FTV_M2M_SETTINGS SET VAL = COALESCE(:V, 0) WHERE NAME = 'FILER_RUBRICA' AND VAL IS NULL;
|
||||
END`, login)
|
||||
}
|
||||
|
||||
// depoFBReadAll — право логина bj читать учёт: все таблицы Fancy, кроме таблиц обмена (на них — свои права).
|
||||
// Имена без кавычек: так команда работает и в диалекте 1 (isql по скрипту), где "…" — строка.
|
||||
func depoFBReadAll(login string) string {
|
||||
return fmt.Sprintf(`EXECUTE BLOCK AS
|
||||
DECLARE VARIABLE N VARCHAR(31);
|
||||
BEGIN
|
||||
FOR SELECT TRIM(RDB$RELATION_NAME) FROM RDB$RELATIONS
|
||||
WHERE COALESCE(RDB$SYSTEM_FLAG, 0) = 0 AND RDB$RELATION_NAME NOT STARTING WITH 'FTV_M2M'
|
||||
INTO :N DO
|
||||
EXECUTE STATEMENT 'GRANT SELECT ON ' || N || ' TO %s';
|
||||
END`, login)
|
||||
}
|
||||
|
||||
var (
|
||||
fbSetTermRe = regexp.MustCompile(`(?i)^SET\s+TERM\s+(\S+)$`)
|
||||
fbIsqlCmdRe = regexp.MustCompile(`(?i)^(COMMIT(\s+WORK)?|SET\s+\w+.*)$`)
|
||||
)
|
||||
|
||||
// fbStatements — isql-скрипт на команды для драйвера: SET TERM меняет разделитель, COMMIT и прочие SET — команды
|
||||
// isql (у драйвера каждая команда фиксируется сама), комментарии вне команд отбрасываются.
|
||||
func fbStatements(script string) []string {
|
||||
term := ";"
|
||||
var out []string
|
||||
var cur strings.Builder
|
||||
flush := func() {
|
||||
s := strings.TrimSpace(cur.String())
|
||||
cur.Reset()
|
||||
if body := strings.TrimSpace(fbStripComments(s)); body != "" {
|
||||
if m := fbSetTermRe.FindStringSubmatch(body); m != nil {
|
||||
term = m[1]
|
||||
return
|
||||
}
|
||||
if fbIsqlCmdRe.MatchString(body) {
|
||||
return
|
||||
}
|
||||
out = append(out, s)
|
||||
}
|
||||
}
|
||||
for i := 0; i < len(script); i++ {
|
||||
c := script[i]
|
||||
switch {
|
||||
case c == '\'' || c == '"':
|
||||
j := i + 1
|
||||
for j < len(script) {
|
||||
if script[j] == c {
|
||||
if j+1 < len(script) && script[j+1] == c {
|
||||
j += 2
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
j++
|
||||
}
|
||||
if j >= len(script) {
|
||||
j = len(script) - 1
|
||||
}
|
||||
cur.WriteString(script[i : j+1])
|
||||
i = j
|
||||
case c == '/' && i+1 < len(script) && script[i+1] == '*':
|
||||
j := strings.Index(script[i+2:], "*/")
|
||||
end := len(script)
|
||||
if j >= 0 {
|
||||
end = i + 2 + j + 2
|
||||
}
|
||||
cur.WriteString(script[i:end])
|
||||
i = end - 1
|
||||
case c == '-' && i+1 < len(script) && script[i+1] == '-':
|
||||
j := strings.IndexByte(script[i:], '\n')
|
||||
if j < 0 {
|
||||
j = len(script) - i
|
||||
}
|
||||
cur.WriteString(script[i : i+j])
|
||||
i += j - 1
|
||||
case strings.HasPrefix(script[i:], term):
|
||||
i += len(term) - 1
|
||||
flush()
|
||||
default:
|
||||
cur.WriteByte(c)
|
||||
}
|
||||
}
|
||||
flush()
|
||||
return out
|
||||
}
|
||||
|
||||
// fbStripComments — текст без комментариев (чтобы узнать команду isql или пустой кусок).
|
||||
func fbStripComments(s string) string {
|
||||
var b strings.Builder
|
||||
for i := 0; i < len(s); i++ {
|
||||
switch {
|
||||
case s[i] == '/' && i+1 < len(s) && s[i+1] == '*':
|
||||
j := strings.Index(s[i+2:], "*/")
|
||||
if j < 0 {
|
||||
return b.String()
|
||||
}
|
||||
i += j + 3
|
||||
case s[i] == '-' && i+1 < len(s) && s[i+1] == '-':
|
||||
j := strings.IndexByte(s[i:], '\n')
|
||||
if j < 0 {
|
||||
return b.String()
|
||||
}
|
||||
i += j
|
||||
default:
|
||||
b.WriteByte(s[i])
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// fbExecScript — выполнить скрипт по командам; ошибка — с началом команды.
|
||||
func fbExecScript(ctx context.Context, db *sql.DB, what, script string) error {
|
||||
for _, st := range fbStatements(script) {
|
||||
if _, err := db.ExecContext(ctx, st); err != nil {
|
||||
return fmt.Errorf("%s: %s → %s", what, depoStmtHead(fbStripComments(st)), quikErrorDetail(err))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// fbUser — создать пользователя сервера Firebird или сменить ему пароль (повторная подготовка). Firebird 2.5
|
||||
// меняет пользователей при фиксации транзакции: неудачный CREATE USER (логин уже есть) — откат, иначе ошибка
|
||||
// фиксации остаётся в подключении и повторяется на каждой следующей команде.
|
||||
func fbUser(ctx context.Context, db *sql.DB, login, pass string) error {
|
||||
err := fbExecTx(ctx, db, "CREATE USER "+login+" PASSWORD '"+sqlStr(pass)+"'")
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
m := strings.ToLower(err.Error())
|
||||
if !strings.Contains(m, "rdb$users") && !strings.Contains(m, "already") && !strings.Contains(m, "уже") {
|
||||
return err
|
||||
}
|
||||
return fbExecTx(ctx, db, "ALTER USER "+login+" PASSWORD '"+sqlStr(pass)+"'")
|
||||
}
|
||||
|
||||
// fbExecTx — команда в своей транзакции; ошибка выполнения или фиксации — откат.
|
||||
func fbExecTx(ctx context.Context, db *sql.DB, q string) error {
|
||||
tx, err := db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tx.ExecContext(ctx, q); err != nil {
|
||||
_ = tx.Rollback()
|
||||
return err
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
_ = tx.Rollback()
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// depoFBUDFCheck — функции Fancy (UDF ib_Fansy) на сервере: без них учётные таблицы не читаются.
|
||||
func depoFBUDFCheck(ctx context.Context, db *sql.DB) error {
|
||||
var s sql.NullString
|
||||
err := db.QueryRowContext(ctx, "SELECT FIRST 1 NUM_DATE FROM OD_DOCS").Scan(&s)
|
||||
if err == nil || err == sql.ErrNoRows {
|
||||
return nil
|
||||
}
|
||||
m := err.Error()
|
||||
if strings.Contains(m, "is not defined") || strings.Contains(m, "entrypoint") {
|
||||
return fmt.Errorf("сервер Firebird не находит функции Fancy (UDF ib_Fansy): %s. Библиотека ib_Fansy должна лежать в папке UDF сервера Firebird — так же, как для работы самой Fancy", quikErrorDetail(err))
|
||||
}
|
||||
return fmt.Errorf("чтение OD_DOCS: %s", quikErrorDetail(err))
|
||||
}
|
||||
|
||||
// depoFBSettings — привязки FTV_M2M_SETTINGS после установки: «ИМЯ=значение» и список незаполненных.
|
||||
func depoFBSettings(ctx context.Context, db *sql.DB) (set, empty []string) {
|
||||
rows, err := db.QueryContext(ctx, "SELECT NAME, VAL FROM FTV_M2M_SETTINGS ORDER BY NAME")
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var n string
|
||||
var v sql.NullInt64
|
||||
if rows.Scan(&n, &v) != nil {
|
||||
continue
|
||||
}
|
||||
n = strings.TrimSpace(n)
|
||||
if v.Valid {
|
||||
set = append(set, fmt.Sprintf("%s=%d", n, v.Int64))
|
||||
} else {
|
||||
empty = append(empty, n)
|
||||
}
|
||||
}
|
||||
return set, empty
|
||||
}
|
||||
|
||||
// depoFBSetup — «bj сделает сам» для Firebird логином администратора adm: проверка базы, логин bj, таблицы
|
||||
// обмена, оператор и привязки Fancy, процедуры, права. Возвращает итог привязок (что не определилось — warn).
|
||||
func depoFBSetup(ctx context.Context, adm FancyMSSQLSettings, db, login, pass string, step depoStep) error {
|
||||
if !fbLoginRe.MatchString(login) {
|
||||
return fmt.Errorf("логин bj для Firebird — латинские буквы, цифры и _, начинается с SYSDBA (например, %s): Fancy даёт права на документы только логинам SYSDBA…", depoFBDefaultLogin)
|
||||
}
|
||||
step("connect", "run", "Подключаюсь к базе "+db+" на "+adm.Server+" логином администратора «"+adm.User+"»")
|
||||
conn, err := fancyOpen(adm, db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
cctx, cancel := context.WithTimeout(ctx, 5*time.Minute)
|
||||
defer cancel()
|
||||
if err := conn.PingContext(cctx); err != nil {
|
||||
return fmt.Errorf("к базе %s на %s логином %s подключиться не удалось — %s", db, adm.Server, adm.User, quikErrorDetail(err))
|
||||
}
|
||||
step("connect", "ok", "")
|
||||
step("isdepo", "run", "Проверяю, что это база учёта DEPO и функции Fancy на сервере доступны")
|
||||
if err := depoCheckIsDepo(cctx, adm, db); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := depoFBUDFCheck(cctx, conn); err != nil {
|
||||
return err
|
||||
}
|
||||
step("isdepo", "ok", "")
|
||||
step("apply", "run", "Создаю логин bj «"+login+"», таблицы обмена FTV_M2M* и оператора Fancy для bj")
|
||||
if err := fbUser(cctx, conn, login, pass); err != nil {
|
||||
return fmt.Errorf("логин %s: %s (создавать пользователей сервера Firebird может только SYSDBA)", login, quikErrorDetail(err))
|
||||
}
|
||||
if err := fbExecScript(cctx, conn, "таблицы обмена", depoFBScript("00_FTV_M2M_TABLES.sql")); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := conn.ExecContext(cctx, depoFBBindings(login)); err != nil {
|
||||
return fmt.Errorf("оператор и привязки Fancy: %s", quikErrorDetail(err))
|
||||
}
|
||||
step("apply", "ok", "")
|
||||
step("procs", "run", "Устанавливаю процедуры обработки и выдаю права логину bj")
|
||||
if err := fbExecScript(cctx, conn, "процедуры", depoFBScript("10_FTV_M2M_PROCS.sql")); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fbExecScript(cctx, conn, "права", depoFBGrants(login)); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := conn.ExecContext(cctx, depoFBReadAll(login)); err != nil {
|
||||
return fmt.Errorf("право чтения учёта логину %s: %s", login, quikErrorDetail(err))
|
||||
}
|
||||
step("procs", "ok", "")
|
||||
set, empty := depoFBSettings(cctx, conn)
|
||||
if len(empty) > 0 {
|
||||
step("bindings", "warn", "Не определены по справочникам Fancy: "+strings.Join(empty, ", ")+
|
||||
" — впишите значения в таблицу FTV_M2M_SETTINGS (UPDATE FTV_M2M_SETTINGS SET VAL = … WHERE NAME = '…'), иначе обработка остановится с этой ошибкой. Определены: "+strings.Join(set, ", "))
|
||||
} else {
|
||||
step("bindings", "ok", "Привязки к справочникам Fancy: "+strings.Join(set, ", "))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// win1251 — текст скрипта для isql -ch WIN1251 (знаки вне WIN1251 — заменяются).
|
||||
func win1251(s string) []byte {
|
||||
b, err := encoding.ReplaceUnsupported(charmap.Windows1251.NewEncoder()).Bytes([]byte(s))
|
||||
if err != nil {
|
||||
return []byte(s)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// depoSetupScriptFB — скрипт для администратора (isql, файл в WIN1251): то же, что depoFBSetup. pass="" — без
|
||||
// создания логина (обновление процедур: логин bj уже есть).
|
||||
func depoSetupScriptFB(f FancyMSSQLSettings, db, login, pass string) string {
|
||||
var b strings.Builder
|
||||
if pass == "" {
|
||||
fmt.Fprintf(&b, `/* Bridge&Join: таблицы обмена, привязки, процедуры обработки и права логину bj %[3]s (Fancy на Firebird).
|
||||
Запуск под SYSDBA утилитой isql с кодировкой WIN1251:
|
||||
isql -ch WIN1251 -user SYSDBA -password <пароль> -i bj-depo-procs-firebird.sql %[1]s:%[2]s
|
||||
Можно запускать повторно: недостающее добавляется, процедуры заменяются, заполненные привязки не меняются. */
|
||||
|
||||
`, f.Server, db, login)
|
||||
depoFBScriptBody(&b, login)
|
||||
return b.String()
|
||||
}
|
||||
fmt.Fprintf(&b, `/* Bridge&Join: подготовка базы учёта DEPO (Fancy на Firebird) для bj.
|
||||
Запуск под SYSDBA утилитой isql с кодировкой WIN1251:
|
||||
isql -ch WIN1251 -user SYSDBA -password <пароль> -i bj-depo-setup-firebird.sql %[1]s:%[2]s
|
||||
Создаёт логин bj %[3]s (пользователь сервера Firebird: только выданные ниже права; Fancy даёт логинам SYSDBA…
|
||||
права на документы, а по оператору Fancy — депозитарий и нумерацию), таблицы обмена FTV_M2M*, оператора
|
||||
Fancy для bj, кодировщик M2M и привязки к справочникам Fancy (FTV_M2M_SETTINGS — только пустые), процедуры
|
||||
обработки и права. Учётные данные не меняет. Можно запускать повторно.
|
||||
Если логин %[3]s на сервере уже есть, CREATE USER выдаст ошибку — это нормально, следующая строка ставит пароль.
|
||||
В скрипте пароль логина bj — после выполнения файл удалите. */
|
||||
|
||||
CREATE USER %[3]s PASSWORD '%[4]s';
|
||||
ALTER USER %[3]s PASSWORD '%[4]s';
|
||||
COMMIT;
|
||||
|
||||
`, f.Server, db, login, sqlStr(pass))
|
||||
depoFBScriptBody(&b, login)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// depoFBScriptBody — таблицы, привязки, процедуры и права в скрипт isql.
|
||||
func depoFBScriptBody(b *strings.Builder, login string) {
|
||||
b.WriteString(depoFBScript("00_FTV_M2M_TABLES.sql"))
|
||||
b.WriteString("\n/* Оператор Fancy для bj, кодировщик M2M, привязки к справочникам */\nSET TERM ^ ;\n")
|
||||
b.WriteString(depoFBBindings(login))
|
||||
b.WriteString("^\nSET TERM ; ^\nCOMMIT;\n\n")
|
||||
b.WriteString(depoFBScript("10_FTV_M2M_PROCS.sql"))
|
||||
b.WriteString("\n")
|
||||
b.WriteString(depoFBGrants(login))
|
||||
b.WriteString("\n/* Чтение учёта логином bj */\nSET TERM ^ ;\n")
|
||||
b.WriteString(depoFBReadAll(login))
|
||||
b.WriteString("^\nSET TERM ; ^\nCOMMIT;\n\n/* Привязки: пустые значения нужно вписать вручную */\nSELECT NAME, VAL FROM FTV_M2M_SETTINGS;\n")
|
||||
}
|
||||
|
||||
// depoFBHash — отпечаток установки для Firebird (плашка «процедуры DEPO обновились»).
|
||||
func depoFBHash() []byte {
|
||||
var b strings.Builder
|
||||
for _, n := range depoFBScriptFiles {
|
||||
b.WriteString(n + "\n" + depoFBScript(n) + "\n")
|
||||
}
|
||||
b.WriteString(depoFBBindings("") + depoFBReadAll(""))
|
||||
return []byte(b.String())
|
||||
}
|
||||
|
||||
// depoProcsPreflightFB — «Проверить возможности» для Firebird: подключение администратора, база DEPO, функции
|
||||
// Fancy, какие процедуры уже есть.
|
||||
func depoProcsPreflightFB(ctx context.Context, f FancyMSSQLSettings, adminUser, adminPass string) (string, bool, error) {
|
||||
adm := f
|
||||
if u := strings.TrimSpace(adminUser); u != "" {
|
||||
adm.User, adm.Password = u, adminPass
|
||||
}
|
||||
db, err := fancyOpen(adm, 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(), adm.User, quikErrorDetail(err))
|
||||
}
|
||||
if err := depoCheckIsDepo(ctx, adm, f.DepoDB()); err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
if err := depoFBUDFCheck(ctx, db); err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
missing := depoMissing(ctx, db, depoBj2depoProcs, "P")
|
||||
var b strings.Builder
|
||||
fmt.Fprintf(&b, "Сервер %s, база %s, логин %s.\n", f.Server, f.DepoDB(), adm.User)
|
||||
if len(missing) == 0 {
|
||||
b.WriteString("Процедуры обработки: все на месте (будут заменены CREATE OR ALTER).\n")
|
||||
} else {
|
||||
fmt.Fprintf(&b, "Процедуры обработки: нет %s.\n", strings.Join(missing, ", "))
|
||||
}
|
||||
canApply := strings.EqualFold(adm.User, "SYSDBA")
|
||||
if !canApply {
|
||||
b.WriteString("Нужен логин SYSDBA: процедуры и права в базе Fancy ставит администратор сервера Firebird.\n")
|
||||
}
|
||||
fmt.Fprintf(&b, "Будет выполнено: таблицы обмена (недостающее), привязки к справочникам (только пустые), процедуры обработки, права логину %s.", depoSetupLogin(f))
|
||||
return b.String(), canApply, nil
|
||||
}
|
||||
|
||||
// depoProcsInstallFB — «Установить» для Firebird: та же подготовка, что в мастере, с прежним логином и паролем bj.
|
||||
func depoProcsInstallFB(ctx context.Context, f FancyMSSQLSettings, adminUser, adminPass string) (string, error) {
|
||||
adm := f
|
||||
if u := strings.TrimSpace(adminUser); u != "" {
|
||||
adm.User, adm.Password = u, adminPass
|
||||
}
|
||||
if !strings.EqualFold(adm.User, "SYSDBA") {
|
||||
return "", fmt.Errorf("введите логин и пароль SYSDBA: процедуры и права в базе Fancy ставит администратор сервера Firebird")
|
||||
}
|
||||
login := depoSetupLogin(f)
|
||||
if strings.TrimSpace(f.Password) == "" {
|
||||
return "", fmt.Errorf("у bj нет пароля логина %s — подготовьте базу в мастере (шаг «Учёт DEPO»)", login)
|
||||
}
|
||||
var notes []string
|
||||
step := func(key, state, text string) {
|
||||
if state == "warn" {
|
||||
notes = append(notes, text)
|
||||
}
|
||||
}
|
||||
if err := depoFBSetup(ctx, adm, f.DepoDB(), login, f.Password, step); err != nil {
|
||||
return "", err
|
||||
}
|
||||
msg := "таблицы обмена, привязки, процедуры обработки и права логину " + login + " установлены"
|
||||
if len(notes) > 0 {
|
||||
msg += ". " + strings.Join(notes, " ")
|
||||
}
|
||||
return msg, nil
|
||||
}
|
||||
@@ -75,6 +75,9 @@ func depoSnapshotTake(ctx context.Context, fs FancyMSSQLSettings) (*DepoSnapshot
|
||||
if strings.TrimSpace(fs.Server) == "" {
|
||||
return nil, fmt.Errorf("подключение к DEPO не настроено")
|
||||
}
|
||||
if fs.IsFirebird() { // снимок читает каталог SQL Server (sys.objects, sys.sql_modules)
|
||||
return nil, fmt.Errorf("снимок объектов DEPO снимается только с Microsoft SQL Server")
|
||||
}
|
||||
db, err := fancyOpen(fs, fs.DepoDB())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -158,7 +158,7 @@ func depoInPosted(ctx context.Context, rc *RuntimeConfig, guid string) (gate, po
|
||||
|
||||
func depoHasColumn(ctx context.Context, db *sql.DB, table, col string) bool {
|
||||
var n int
|
||||
_ = db.QueryRowContext(ctx, "SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME=@t AND COLUMN_NAME=@c", sql.Named("t", table), sql.Named("c", col)).Scan(&n)
|
||||
_ = db.QueryRowContext(ctx, fsql.Cat04, sql.Named("t", table), sql.Named("c", col)).Scan(&n)
|
||||
return n > 0
|
||||
}
|
||||
|
||||
@@ -706,6 +706,9 @@ func depoWriteHandbookCtx(pctx context.Context, rc *RuntimeConfig) error {
|
||||
ctx, cancel := context.WithTimeout(pctx, 60*time.Second)
|
||||
defer cancel()
|
||||
if !depoTableExists(ctx, db, "FTV_M2M_HANDBOOK") {
|
||||
if rc.Snapshot().Fancy.IsFirebird() { // у логина bj в Firebird нет права создавать таблицы — их ставит подготовка базы
|
||||
return fmt.Errorf("нет таблицы FTV_M2M_HANDBOOK — подготовьте базу DEPO: мастер, шаг «Учёт DEPO»")
|
||||
}
|
||||
if _, err := db.ExecContext(ctx, depoTableSpecByName("FTV_M2M_HANDBOOK").createSQL(false)); err != nil {
|
||||
return fmt.Errorf("создание FTV_M2M_HANDBOOK: %w — создайте таблицы: Настройка → Обмен Fancy → «Трансферные таблицы DEPO»", err)
|
||||
}
|
||||
@@ -827,6 +830,9 @@ func depoJobProcName(f FancyMSSQLSettings) string {
|
||||
if p := strings.TrimSpace(f.DepoJobProc); p != "" {
|
||||
return p
|
||||
}
|
||||
if f.IsFirebird() { // схем у Firebird нет
|
||||
return "FTV_M2M_JOB"
|
||||
}
|
||||
return "dbo.FTV_M2M_JOB"
|
||||
}
|
||||
|
||||
@@ -1012,7 +1018,7 @@ func depoJobRunnerCheck(ctx context.Context, rc *RuntimeConfig, adminUser, admin
|
||||
}
|
||||
defer db.Close()
|
||||
var n int
|
||||
_ = db.QueryRowContext(ctx, "SELECT COUNT(*) FROM fn_my_permissions(@p,'OBJECT') WHERE permission_name='EXECUTE'", sql.Named("p", depoJobProcName(f))).Scan(&n)
|
||||
_ = db.QueryRowContext(ctx, fsql.Cat05, sql.Named("p", depoJobProcName(f))).Scan(&n)
|
||||
if n == 0 {
|
||||
return "у логина " + f.User + " нет права EXECUTE на " + depoJobProcName(f) + " — выполните на сервере: " + strings.ReplaceAll(depoJobGrantScript(f), "\n", " "), false
|
||||
}
|
||||
@@ -1610,7 +1616,7 @@ func depoMissing(ctx context.Context, db *sql.DB, names []string, typ string) []
|
||||
var out []string
|
||||
for _, n := range names {
|
||||
var ok int
|
||||
if err := db.QueryRowContext(ctx, "SELECT CASE WHEN OBJECT_ID(@n, @t) IS NULL THEN 0 ELSE 1 END", sql.Named("n", "dbo."+n), sql.Named("t", typ)).Scan(&ok); err == nil && ok == 0 {
|
||||
if err := db.QueryRowContext(ctx, fsql.Cat06, sql.Named("n", "dbo."+n), sql.Named("t", typ)).Scan(&ok); err == nil && ok == 0 {
|
||||
out = append(out, n)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ func depoJobRunAs(f FancyMSSQLSettings) string {
|
||||
func depoRunAsCheck(ctx context.Context, db *sql.DB, f FancyMSSQLSettings) (bool, string) {
|
||||
name := depoJobRunAs(f)
|
||||
var id sql.NullInt64
|
||||
if err := db.QueryRowContext(ctx, "SELECT USER_ID(@n)", sql.Named("n", name)).Scan(&id); err != nil {
|
||||
if err := db.QueryRowContext(ctx, fsql.Cat07, sql.Named("n", name)).Scan(&id); err != nil {
|
||||
return false, "не удалось проверить пользователя " + name + ": " + quikErrorDetail(err)
|
||||
}
|
||||
if !id.Valid {
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
/* Bridge&Join — трансферные таблицы обмена M2M для Fancy DEPO на Firebird 2.5.
|
||||
Аналог таблиц FTV_M2M* версии MSSQL. Отличия:
|
||||
- ID — генератор + триггер (в 2.5 нет IDENTITY);
|
||||
- GUID — CHAR(36) в верхнем регистре (как отдаёт MSSQL uniqueidentifier);
|
||||
- COMMENT / M2M_REJECT_CODE varchar(MAX) — BLOB SUB_TYPE TEXT;
|
||||
- ACTIVE bit — SMALLINT; AMOUNT numeric(25,7) — DOUBLE PRECISION, как количества в Fancy;
|
||||
- FTV_M2M_SETTINGS — привязки к справочникам клиента (в MSSQL-версии они зашиты в тексты процедур).
|
||||
Скрипт можно выполнять повторно: существующие объекты не пересоздаются (кроме триггеров и исключения). */
|
||||
|
||||
SET TERM ^ ;
|
||||
|
||||
EXECUTE BLOCK AS
|
||||
BEGIN
|
||||
IF (NOT EXISTS (SELECT 1 FROM RDB$RELATIONS WHERE RDB$RELATION_NAME = 'FTV_M2M')) THEN
|
||||
EXECUTE STATEMENT 'CREATE TABLE FTV_M2M (
|
||||
ID INTEGER NOT NULL PRIMARY KEY,
|
||||
GUID CHAR(36) NOT NULL,
|
||||
STATE VARCHAR(64) DEFAULT ''TODO'' NOT NULL,
|
||||
TIMENEW TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
TIMEBEG TIMESTAMP,
|
||||
TIMEEND TIMESTAMP,
|
||||
COMMENT BLOB SUB_TYPE TEXT,
|
||||
DIRECTION VARCHAR(4) NOT NULL,
|
||||
CONTRAGENT_M2M_CODE VARCHAR(255) NOT NULL,
|
||||
DEPONENT_NAME VARCHAR(255) NOT NULL,
|
||||
DEPONENT_DOC VARCHAR(255) NOT NULL,
|
||||
DOC INTEGER,
|
||||
CONTRAGENT INTEGER,
|
||||
DEPONENT INTEGER)';
|
||||
|
||||
IF (NOT EXISTS (SELECT 1 FROM RDB$RELATIONS WHERE RDB$RELATION_NAME = 'FTV_M2M_LINES')) THEN
|
||||
EXECUTE STATEMENT 'CREATE TABLE FTV_M2M_LINES (
|
||||
ID INTEGER NOT NULL PRIMARY KEY,
|
||||
GUID CHAR(36) NOT NULL,
|
||||
REFERENCE VARCHAR(36),
|
||||
STATE VARCHAR(64) DEFAULT ''TODO'' NOT NULL,
|
||||
TIMENEW TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
TIMEBEG TIMESTAMP,
|
||||
TIMEEND TIMESTAMP,
|
||||
ISIN VARCHAR(12),
|
||||
SEC_CODE VARCHAR(255),
|
||||
AMOUNT DOUBLE PRECISION,
|
||||
PLACE_NUMBER VARCHAR(128),
|
||||
LINK_NUMBER VARCHAR(64),
|
||||
DEPO_NUMBER VARCHAR(128),
|
||||
DOC INTEGER,
|
||||
DOL INTEGER,
|
||||
SHARE INTEGER,
|
||||
PLACE INTEGER,
|
||||
DEPO INTEGER,
|
||||
COMMENT BLOB SUB_TYPE TEXT)';
|
||||
|
||||
IF (NOT EXISTS (SELECT 1 FROM RDB$RELATIONS WHERE RDB$RELATION_NAME = 'FTV_M2M_EVENTS')) THEN
|
||||
EXECUTE STATEMENT 'CREATE TABLE FTV_M2M_EVENTS (
|
||||
ID INTEGER NOT NULL PRIMARY KEY,
|
||||
GUID CHAR(36),
|
||||
REFERENCE VARCHAR(36),
|
||||
STATE VARCHAR(64) DEFAULT ''TODO'' NOT NULL,
|
||||
TIMENEW TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
TIMEBEG TIMESTAMP,
|
||||
TIMEEND TIMESTAMP,
|
||||
COMMENT BLOB SUB_TYPE TEXT,
|
||||
EVENT_TYPE VARCHAR(64) NOT NULL,
|
||||
EVENT_DATE TIMESTAMP NOT NULL,
|
||||
M2M_REJECT_CODE VARCHAR(1000),
|
||||
PARAMETERS VARCHAR(4000))';
|
||||
|
||||
IF (NOT EXISTS (SELECT 1 FROM RDB$RELATIONS WHERE RDB$RELATION_NAME = 'FTV_M2M_HANDBOOK')) THEN
|
||||
EXECUTE STATEMENT 'CREATE TABLE FTV_M2M_HANDBOOK (
|
||||
ID INTEGER NOT NULL PRIMARY KEY,
|
||||
INN VARCHAR(12) NOT NULL,
|
||||
PARTICIPANT_CODE VARCHAR(12),
|
||||
KIND VARCHAR(8) NOT NULL,
|
||||
NAME_FULL VARCHAR(255),
|
||||
NAME_SHORT VARCHAR(128),
|
||||
NAME_DISPLAY VARCHAR(255),
|
||||
NAME_ENG VARCHAR(255),
|
||||
HANDBOOK_AT TIMESTAMP,
|
||||
UPDATED_AT TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
ACTIVE SMALLINT DEFAULT 1 NOT NULL)';
|
||||
|
||||
/* Привязки к справочникам Fancy клиента: в MSSQL-версии (Фонтвьель) — числа в текстах процедур.
|
||||
Заполняются при установке; пустое значение — процедура останавливается с понятной ошибкой. */
|
||||
IF (NOT EXISTS (SELECT 1 FROM RDB$RELATIONS WHERE RDB$RELATION_NAME = 'FTV_M2M_SETTINGS')) THEN
|
||||
EXECUTE STATEMENT 'CREATE TABLE FTV_M2M_SETTINGS (
|
||||
NAME VARCHAR(64) NOT NULL PRIMARY KEY,
|
||||
VAL INTEGER,
|
||||
COMMENT VARCHAR(255))';
|
||||
|
||||
IF (NOT EXISTS (SELECT 1 FROM RDB$GENERATORS WHERE RDB$GENERATOR_NAME = 'FTV_M2M_GEN')) THEN
|
||||
EXECUTE STATEMENT 'CREATE GENERATOR FTV_M2M_GEN';
|
||||
IF (NOT EXISTS (SELECT 1 FROM RDB$GENERATORS WHERE RDB$GENERATOR_NAME = 'FTV_M2M_LINES_GEN')) THEN
|
||||
EXECUTE STATEMENT 'CREATE GENERATOR FTV_M2M_LINES_GEN';
|
||||
IF (NOT EXISTS (SELECT 1 FROM RDB$GENERATORS WHERE RDB$GENERATOR_NAME = 'FTV_M2M_EVENTS_GEN')) THEN
|
||||
EXECUTE STATEMENT 'CREATE GENERATOR FTV_M2M_EVENTS_GEN';
|
||||
IF (NOT EXISTS (SELECT 1 FROM RDB$GENERATORS WHERE RDB$GENERATOR_NAME = 'FTV_M2M_HANDBOOK_GEN')) THEN
|
||||
EXECUTE STATEMENT 'CREATE GENERATOR FTV_M2M_HANDBOOK_GEN';
|
||||
|
||||
IF (NOT EXISTS (SELECT 1 FROM RDB$INDICES WHERE RDB$INDEX_NAME = 'FTV_M2M_GUID')) THEN
|
||||
EXECUTE STATEMENT 'CREATE INDEX FTV_M2M_GUID ON FTV_M2M (GUID)';
|
||||
IF (NOT EXISTS (SELECT 1 FROM RDB$INDICES WHERE RDB$INDEX_NAME = 'FTV_M2M_LINES_GUID')) THEN
|
||||
EXECUTE STATEMENT 'CREATE INDEX FTV_M2M_LINES_GUID ON FTV_M2M_LINES (GUID)';
|
||||
IF (NOT EXISTS (SELECT 1 FROM RDB$INDICES WHERE RDB$INDEX_NAME = 'FTV_M2M_EVENTS_STATE')) THEN
|
||||
EXECUTE STATEMENT 'CREATE INDEX FTV_M2M_EVENTS_STATE ON FTV_M2M_EVENTS (STATE)';
|
||||
IF (NOT EXISTS (SELECT 1 FROM RDB$INDICES WHERE RDB$INDEX_NAME = 'FTV_M2M_EVENTS_GUID')) THEN
|
||||
EXECUTE STATEMENT 'CREATE INDEX FTV_M2M_EVENTS_GUID ON FTV_M2M_EVENTS (GUID)';
|
||||
IF (NOT EXISTS (SELECT 1 FROM RDB$INDICES WHERE RDB$INDEX_NAME = 'FTV_M2M_HANDBOOK_INN')) THEN
|
||||
EXECUTE STATEMENT 'CREATE INDEX FTV_M2M_HANDBOOK_INN ON FTV_M2M_HANDBOOK (INN)';
|
||||
END^
|
||||
|
||||
/* таблицы — до триггеров: DDL блока выполняется в его транзакции */
|
||||
COMMIT^
|
||||
|
||||
CREATE OR ALTER TRIGGER FTV_M2M_BI FOR FTV_M2M ACTIVE BEFORE INSERT POSITION 0 AS
|
||||
BEGIN
|
||||
IF (NEW.ID IS NULL OR NEW.ID = 0) THEN NEW.ID = GEN_ID(FTV_M2M_GEN, 1);
|
||||
NEW.GUID = UPPER(NEW.GUID);
|
||||
END^
|
||||
|
||||
CREATE OR ALTER TRIGGER FTV_M2M_LINES_BI FOR FTV_M2M_LINES ACTIVE BEFORE INSERT POSITION 0 AS
|
||||
BEGIN
|
||||
IF (NEW.ID IS NULL OR NEW.ID = 0) THEN NEW.ID = GEN_ID(FTV_M2M_LINES_GEN, 1);
|
||||
NEW.GUID = UPPER(NEW.GUID);
|
||||
END^
|
||||
|
||||
CREATE OR ALTER TRIGGER FTV_M2M_EVENTS_BI FOR FTV_M2M_EVENTS ACTIVE BEFORE INSERT POSITION 0 AS
|
||||
BEGIN
|
||||
IF (NEW.ID IS NULL OR NEW.ID = 0) THEN NEW.ID = GEN_ID(FTV_M2M_EVENTS_GEN, 1);
|
||||
NEW.GUID = UPPER(NEW.GUID);
|
||||
END^
|
||||
|
||||
CREATE OR ALTER TRIGGER FTV_M2M_HANDBOOK_BI FOR FTV_M2M_HANDBOOK ACTIVE BEFORE INSERT POSITION 0 AS
|
||||
BEGIN
|
||||
IF (NEW.ID IS NULL OR NEW.ID = 0) THEN NEW.ID = GEN_ID(FTV_M2M_HANDBOOK_GEN, 1);
|
||||
END^
|
||||
|
||||
SET TERM ; ^
|
||||
|
||||
/* Ошибка обработки: процедуры поднимают её с текстом причины (аналог raiserror MSSQL-версии). */
|
||||
CREATE OR ALTER EXCEPTION FTV_M2M_ERROR 'Ошибка обработки M2M';
|
||||
|
||||
/* Строки настроек без значений: ID у каждого клиента свои, в комментарии — значение у Фонтвьель (MSSQL) для
|
||||
ориентира. Вставляются только отсутствующие строки — повторный запуск не затирает заполненные значения. */
|
||||
SET TERM ^ ;
|
||||
EXECUTE BLOCK AS
|
||||
BEGIN
|
||||
IF (NOT EXISTS (SELECT 1 FROM FTV_M2M_SETTINGS WHERE NAME = 'WHOS')) THEN
|
||||
INSERT INTO FTV_M2M_SETTINGS (NAME, VAL, COMMENT) VALUES ('WHOS', NULL, 'OD_DOCS.WHOS — юрлицо-депозитарий (Фонтвьель: 383118)');
|
||||
IF (NOT EXISTS (SELECT 1 FROM FTV_M2M_SETTINGS WHERE NAME = 'OPER_USER')) THEN
|
||||
INSERT INTO FTV_M2M_SETTINGS (NAME, VAL, COMMENT) VALUES ('OPER_USER', NULL, 'OD_OPERATORS.ID — оператор проводок (Фонтвьель: 84, SYSDBA)');
|
||||
IF (NOT EXISTS (SELECT 1 FROM FTV_M2M_SETTINGS WHERE NAME = 'FILER_CAT')) THEN
|
||||
INSERT INTO FTV_M2M_SETTINGS (NAME, VAL, COMMENT) VALUES ('FILER_CAT', NULL, 'OD_DOC_CATS.ID категории D_FILER — входящая запись (Фонтвьель: 255)');
|
||||
IF (NOT EXISTS (SELECT 1 FROM FTV_M2M_SETTINGS WHERE NAME = 'D_NUM')) THEN
|
||||
INSERT INTO FTV_M2M_SETTINGS (NAME, VAL, COMMENT) VALUES ('D_NUM', NULL, 'OD_BALANCES.ID — 98010 НОСТРО депо базовый (Фонтвьель: 313)');
|
||||
IF (NOT EXISTS (SELECT 1 FROM FTV_M2M_SETTINGS WHERE NAME = 'K_NUM')) THEN
|
||||
INSERT INTO FTV_M2M_SETTINGS (NAME, VAL, COMMENT) VALUES ('K_NUM', NULL, 'OD_BALANCES.ID — 98070 ЦБ на торгах (Фонтвьель: 836)');
|
||||
IF (NOT EXISTS (SELECT 1 FROM FTV_M2M_SETTINGS WHERE NAME = 'MARKET')) THEN
|
||||
INSERT INTO FTV_M2M_SETTINGS (NAME, VAL, COMMENT) VALUES ('MARKET', NULL, 'OD_FACES.SELF_ID — ПАО Московская Биржа (Фонтвьель: 8106)');
|
||||
IF (NOT EXISTS (SELECT 1 FROM FTV_M2M_SETTINGS WHERE NAME = 'B_TYPE')) THEN
|
||||
INSERT INTO FTV_M2M_SETTINGS (NAME, VAL, COMMENT) VALUES ('B_TYPE', NULL, 'OD_SYS_TABS DEPO96_DCLA.NUM — «Отчёт депозитария» (Фонтвьель: 31)');
|
||||
IF (NOT EXISTS (SELECT 1 FROM FTV_M2M_SETTINGS WHERE NAME = 'CORR_FACE')) THEN
|
||||
INSERT INTO FTV_M2M_SETTINGS (NAME, VAL, COMMENT) VALUES ('CORR_FACE', NULL, 'OD_FACES.SELF_ID — НКО АО НРД, корреспондент входящей записи (Фонтвьель: 18174)');
|
||||
IF (NOT EXISTS (SELECT 1 FROM FTV_M2M_SETTINGS WHERE NAME = 'FILER_RUBRICA')) THEN
|
||||
INSERT INTO FTV_M2M_SETTINGS (NAME, VAL, COMMENT) VALUES ('FILER_RUBRICA', NULL, 'D_FILER.RUBRICA для отчёта НРД (Фонтвьель: 5, «уточнить в депозитарии»)');
|
||||
END^
|
||||
SET TERM ; ^
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,649 @@
|
||||
/* Bridge&Join — процедуры обработки M2M для Fancy DEPO на Firebird 2.5.
|
||||
Перенос процедур MSSQL-версии (Фонтвьель, тексты от 22.10.2026, OPERATION — от 24.10.2026) на PSQL. Логика та же; отличия:
|
||||
- raiserror — FTV_M2M_FAIL: текст причины кладётся в контекст транзакции и поднимается исключение FTV_M2M_ERROR;
|
||||
FTV_M2M_JOB пишет этот текст в COMMENT (в Firebird 2.5 текст исключения в WHEN не доступен; контекст
|
||||
возвращает строку без кодировки — приводится к WIN1251, кодировке базы Fancy);
|
||||
- ошибка записи откатывает только её изменения (WHEN ANY = точка сохранения), в MSSQL-версии изменения
|
||||
до ошибки оставались;
|
||||
- числа-привязки Фонтвьель (WHOS, счета, биржа, тип основания, НРД, оператор) — из FTV_M2M_SETTINGS;
|
||||
- «execute as user = 'SYSDBA'» нет: процедуры работают правами, выданными им самим (GRANT … TO PROCEDURE,
|
||||
20_FTV_M2M_GRANTS.sql); логину bj нужно только EXECUTE на FTV_M2M_JOB;
|
||||
- SELECT … INTO, который в MSSQL молча брал последнюю из нескольких строк, — SELECT FIRST 1;
|
||||
- в цикле по строкам поручения бумага/счета сбрасываются перед поиском (в MSSQL ненайденное значение
|
||||
оставалось от предыдущей строки).
|
||||
Тексты работают в обоих диалектах: база Fancy — диалект 1 (DATE хранит и время, "…" — строка, а не имя),
|
||||
поэтому COMMENT без кавычек, а дата без времени — через FTV_M2M_DAY; количества — DOUBLE PRECISION, как в
|
||||
Fancy (NUMERIC(18,7) в диалекте 1 и так DOUBLE, а bj подключается в диалекте 3 и создать в базе диалекта 1
|
||||
процедуру с NUMERIC точнее 9 знаков не может). Проверено 27.10.2026 на обезличенной
|
||||
базе Fancy (Firebird 2.5, диалект 1, кодировка NONE) с UDF ib_Fansy и штатными триггерами.
|
||||
Порядок создания: вспомогательные, WIRING, OPERATION, UPDATE_HANDBOOK, EVENTS, CREATE, JOB. */
|
||||
|
||||
SET TERM ^ ;
|
||||
|
||||
/* Ошибка обработки с текстом причины. */
|
||||
CREATE OR ALTER PROCEDURE FTV_M2M_FAIL (MSG VARCHAR(1000)) AS
|
||||
DECLARE VARIABLE R INTEGER;
|
||||
BEGIN
|
||||
R = RDB$SET_CONTEXT('USER_TRANSACTION', 'FTV_M2M_ERR', SUBSTRING(MSG FROM 1 FOR 255));
|
||||
EXCEPTION FTV_M2M_ERROR MSG;
|
||||
END^
|
||||
|
||||
/* Привязка из FTV_M2M_SETTINGS; не заполнена — ошибка с именем настройки. */
|
||||
CREATE OR ALTER PROCEDURE FTV_M2M_GET (NAME VARCHAR(64)) RETURNS (VAL INTEGER) AS
|
||||
BEGIN
|
||||
VAL = NULL;
|
||||
SELECT S.VAL FROM FTV_M2M_SETTINGS S WHERE S.NAME = :NAME INTO :VAL;
|
||||
IF (VAL IS NULL) THEN
|
||||
EXECUTE PROCEDURE FTV_M2M_FAIL('Не заполнена настройка FTV_M2M_SETTINGS.' || NAME || ' (привязка к справочнику Fancy)');
|
||||
END^
|
||||
|
||||
/* «дд.мм.гггг чч:мм:сс» (FORMAT(…, 'dd.MM.yyyy HH:mm:ss') MSSQL-версии). */
|
||||
CREATE OR ALTER PROCEDURE FTV_M2M_DT (T TIMESTAMP) RETURNS (S VARCHAR(19)) AS
|
||||
BEGIN
|
||||
S = NULL;
|
||||
IF (T IS NOT NULL) THEN
|
||||
S = LPAD(EXTRACT(DAY FROM T), 2, '0') || '.' || LPAD(EXTRACT(MONTH FROM T), 2, '0') || '.' || EXTRACT(YEAR FROM T) || ' ' ||
|
||||
LPAD(EXTRACT(HOUR FROM T), 2, '0') || ':' || LPAD(EXTRACT(MINUTE FROM T), 2, '0') || ':' ||
|
||||
LPAD(CAST(FLOOR(EXTRACT(SECOND FROM T)) AS INTEGER), 2, '0');
|
||||
END^
|
||||
|
||||
/* Дата без времени (CONVERT(DATE, …) MSSQL-версии). В диалекте 1 CAST(… AS DATE) время не отрезает. */
|
||||
CREATE OR ALTER PROCEDURE FTV_M2M_DAY (T TIMESTAMP) RETURNS (D TIMESTAMP) AS
|
||||
BEGIN
|
||||
D = NULL;
|
||||
IF (T IS NOT NULL) THEN
|
||||
D = CAST(EXTRACT(YEAR FROM T) || '-' || EXTRACT(MONTH FROM T) || '-' || EXTRACT(DAY FROM T) AS TIMESTAMP);
|
||||
END^
|
||||
|
||||
|
||||
/* Директива «Обработать» для документа «Зачисление» или «Списание». Значения берутся только из самого
|
||||
документа; M2M-поручение и трансферные таблицы не используются. */
|
||||
CREATE OR ALTER PROCEDURE FTV_M2M_PROCESS_WIRING (OPER_ID INTEGER) AS
|
||||
DECLARE VARIABLE USER_ID INTEGER;
|
||||
DECLARE VARIABLE DIRECTION VARCHAR(3);
|
||||
DECLARE VARIABLE D_CAT INTEGER;
|
||||
DECLARE VARIABLE SHARE INTEGER;
|
||||
DECLARE VARIABLE PLACE INTEGER;
|
||||
DECLARE VARIABLE DEPO INTEGER;
|
||||
DECLARE VARIABLE AMOUNT DOUBLE PRECISION;
|
||||
DECLARE VARIABLE CMT VARCHAR(255);
|
||||
DECLARE VARIABLE B_TYPE INTEGER;
|
||||
DECLARE VARIABLE B_NUM VARCHAR(255);
|
||||
DECLARE VARIABLE B_DATE TIMESTAMP;
|
||||
DECLARE VARIABLE WIR_DATE TIMESTAMP;
|
||||
DECLARE VARIABLE D_NUM INTEGER;
|
||||
DECLARE VARIABLE K_NUM INTEGER;
|
||||
DECLARE VARIABLE MARKET INTEGER;
|
||||
DECLARE VARIABLE OP_NAME VARCHAR(255);
|
||||
DECLARE VARIABLE TEXT_B_TYPE VARCHAR(255);
|
||||
DECLARE VARIABLE B_DATE_S VARCHAR(19);
|
||||
DECLARE VARIABLE WIR_ID INTEGER;
|
||||
DECLARE VARIABLE WIR_LIST VARCHAR(255);
|
||||
DECLARE VARIABLE DEB_ID INTEGER;
|
||||
DECLARE VARIABLE KRD_ID INTEGER;
|
||||
DECLARE VARIABLE D_REG_1 INTEGER; DECLARE VARIABLE K_REG_1 INTEGER;
|
||||
DECLARE VARIABLE D_REG_2 INTEGER; DECLARE VARIABLE K_REG_2 INTEGER;
|
||||
DECLARE VARIABLE D_REG_3 INTEGER; DECLARE VARIABLE K_REG_3 INTEGER;
|
||||
DECLARE VARIABLE D_REG_4 INTEGER; DECLARE VARIABLE K_REG_4 INTEGER;
|
||||
DECLARE VARIABLE D_REG_5 INTEGER; DECLARE VARIABLE K_REG_5 INTEGER;
|
||||
DECLARE VARIABLE D_REG_6 INTEGER; DECLARE VARIABLE K_REG_6 INTEGER;
|
||||
DECLARE VARIABLE DIR_ID INTEGER;
|
||||
DECLARE VARIABLE OUT_STATE INTEGER;
|
||||
DECLARE VARIABLE STEP_ID INTEGER;
|
||||
BEGIN
|
||||
EXECUTE PROCEDURE FTV_M2M_GET('OPER_USER') RETURNING_VALUES :USER_ID;
|
||||
|
||||
/* НАПРАВЛЕНИЕ (MSSQL-версия при ненайденном документе молча выходила — здесь ошибка, видно в COMMENT) */
|
||||
IF (EXISTS (SELECT 1 FROM U_OP_INV_ZACH WHERE DOC = :OPER_ID)) THEN DIRECTION = 'IN';
|
||||
ELSE IF (EXISTS (SELECT 1 FROM U_OP_INV_SPIS WHERE DOC = :OPER_ID)) THEN DIRECTION = 'OUT';
|
||||
ELSE EXECUTE PROCEDURE FTV_M2M_FAIL('Не найден документ операции ID = ' || OPER_ID);
|
||||
|
||||
/* ЗНАЧЕНИЯ ПЕРЕМЕННЫХ */
|
||||
IF (DIRECTION = 'IN') THEN
|
||||
BEGIN
|
||||
SELECT D.D_CAT, H.D_NUM, H.K_NUM, H.B_TYPE, H.B_NUM, H.B_DATE, H.SHARE, H.AMOUNT, H.PLACE, H.DEPO, H.MARKET, H.WIR_DATE
|
||||
FROM OD_DOCS D LEFT JOIN U_OP_INV_ZACH H ON H.DOC = D.ID
|
||||
WHERE D.ID = :OPER_ID
|
||||
INTO :D_CAT, :D_NUM, :K_NUM, :B_TYPE, :B_NUM, :B_DATE, :SHARE, :AMOUNT, :PLACE, :DEPO, :MARKET, :WIR_DATE;
|
||||
OP_NAME = 'Зачисление';
|
||||
SELECT C.SELF_ID, C.FACE, 0, 0, 0, :PLACE FROM OD_CLIENTS C WHERE C.ID = :PLACE
|
||||
INTO :D_REG_1, :D_REG_2, :D_REG_3, :D_REG_4, :D_REG_5, :D_REG_6;
|
||||
SELECT C.SELF_ID, :MARKET, 0, :D_REG_1, :PLACE, :DEPO FROM OD_CLIENTS C WHERE C.ID = :DEPO
|
||||
INTO :K_REG_1, :K_REG_2, :K_REG_3, :K_REG_4, :K_REG_5, :K_REG_6;
|
||||
END
|
||||
ELSE
|
||||
BEGIN
|
||||
SELECT D.D_CAT, H.K_NUM /* реверс */, H.D_NUM /* реверс */, H.B_TYPE, H.B_NUM, H.B_DATE, H.SHARE, H.AMOUNT, H.PLACE, H.DEPO, H.MARKET, H.WIR_DATE
|
||||
FROM OD_DOCS D LEFT JOIN U_OP_INV_SPIS H ON H.DOC = D.ID
|
||||
WHERE D.ID = :OPER_ID
|
||||
INTO :D_CAT, :D_NUM, :K_NUM, :B_TYPE, :B_NUM, :B_DATE, :SHARE, :AMOUNT, :PLACE, :DEPO, :MARKET, :WIR_DATE;
|
||||
OP_NAME = 'Списание';
|
||||
SELECT C.SELF_ID, C.FACE, 0, 0, 0, :PLACE FROM OD_CLIENTS C WHERE C.ID = :PLACE
|
||||
INTO :K_REG_1, :K_REG_2, :K_REG_3, :K_REG_4, :K_REG_5, :K_REG_6;
|
||||
SELECT C.SELF_ID, :MARKET, 0, :K_REG_1, :PLACE, :DEPO FROM OD_CLIENTS C WHERE C.ID = :DEPO
|
||||
INTO :D_REG_1, :D_REG_2, :D_REG_3, :D_REG_4, :D_REG_5, :D_REG_6;
|
||||
END
|
||||
|
||||
/* ДОПОЛНИТЕЛЬНЫЕ ПАРАМЕТРЫ: «<тип основания> N<номер> от <дата>»
|
||||
(в MSSQL-версии FORMATMESSAGE начинал строку с пустого @COMMENT — «(null); …») */
|
||||
SELECT FIRST 1 T.NAME FROM OD_SYS_TABS T WHERE T.CODE = 'DEPO96_DCLA' AND T.NUM = :B_TYPE INTO :TEXT_B_TYPE;
|
||||
EXECUTE PROCEDURE FTV_M2M_DT(:B_DATE) RETURNING_VALUES :B_DATE_S;
|
||||
CMT = SUBSTRING((COALESCE(TEXT_B_TYPE, '') || ' N' || COALESCE(B_NUM, '') || ' от ' || COALESCE(SUBSTRING(B_DATE_S FROM 1 FOR 10), '')) FROM 1 FOR 255);
|
||||
|
||||
/* ДИРЕКТИВА */
|
||||
SELECT FIRST 1 T.ID, T.OUT_STATE FROM OD_STEP_TYPES T WHERE T.SYS_NAME = 'PROCESS' AND T.D_CAT = :D_CAT INTO :DIR_ID, :OUT_STATE;
|
||||
STEP_ID = GEN_ID(OD_STEPS_ID_GEN, 1);
|
||||
INSERT INTO OD_STEPS (ID, DOC, S_TYPE, S_DATE, ERR_CODE, OUT_STATE, OPER)
|
||||
VALUES (:STEP_ID, :OPER_ID, :DIR_ID, CURRENT_TIMESTAMP, 0, :OUT_STATE, :USER_ID);
|
||||
|
||||
/* ПРОВОДКА */
|
||||
EXECUTE PROCEDURE OD_PR_INS_WIRS (
|
||||
NULL, 4, :WIR_DATE, :OP_NAME, :CMT, :AMOUNT, :AMOUNT, NULL, 0, 0, NULL, :STEP_ID, NULL, :USER_ID, '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) /* кредит */
|
||||
RETURNING_VALUES :WIR_ID, :WIR_LIST, :DEB_ID, :KRD_ID;
|
||||
|
||||
/* СОСТОЯНИЕ ДОКУМЕНТА */
|
||||
UPDATE OD_DOCS SET STATE = :OUT_STATE WHERE ID = :OPER_ID;
|
||||
END^
|
||||
|
||||
|
||||
/* Создаёт операцию «Зачисление»/«Списание» по строке M2M-поручения, входящую запись (отчёт НРД) и проводит
|
||||
операцию. Возвращает OPER_ID. */
|
||||
CREATE OR ALTER PROCEDURE FTV_M2M_PROCESS_OPERATION (GUID CHAR(36), REFERENCE VARCHAR(255), OPER_DATE TIMESTAMP, PARAMETERS VARCHAR(4000))
|
||||
RETURNS (OPER_ID INTEGER) AS
|
||||
DECLARE VARIABLE WHOS INTEGER;
|
||||
DECLARE VARIABLE ACC_D INTEGER;
|
||||
DECLARE VARIABLE ACC_K INTEGER;
|
||||
DECLARE VARIABLE MARKET INTEGER;
|
||||
DECLARE VARIABLE B_TYPE INTEGER;
|
||||
DECLARE VARIABLE CORR_FACE INTEGER;
|
||||
DECLARE VARIABLE FILER_CAT INTEGER;
|
||||
DECLARE VARIABLE RUBRICA INTEGER;
|
||||
DECLARE VARIABLE CLISER_ID INTEGER;
|
||||
DECLARE VARIABLE CLISER_NUM VARCHAR(64);
|
||||
DECLARE VARIABLE CLISER_DATE TIMESTAMP;
|
||||
DECLARE VARIABLE DIRECTION VARCHAR(64);
|
||||
DECLARE VARIABLE DD_DATE TIMESTAMP;
|
||||
DECLARE VARIABLE DD_NUM VARCHAR(64);
|
||||
DECLARE VARIABLE SHARE INTEGER;
|
||||
DECLARE VARIABLE AMOUNT DOUBLE PRECISION;
|
||||
DECLARE VARIABLE CLIENT INTEGER;
|
||||
DECLARE VARIABLE FACE INTEGER;
|
||||
DECLARE VARIABLE PLACE INTEGER;
|
||||
DECLARE VARIABLE D_CAT INTEGER;
|
||||
DECLARE VARIABLE NUM VARCHAR(255);
|
||||
DECLARE VARIABLE NEW_DATE TIMESTAMP;
|
||||
DECLARE VARIABLE FILER_ID INTEGER;
|
||||
DECLARE VARIABLE FILER_NUM VARCHAR(255);
|
||||
DECLARE VARIABLE FILER_DATE TIMESTAMP;
|
||||
DECLARE VARIABLE STEP_ID INTEGER;
|
||||
DECLARE VARIABLE P VARCHAR(4000);
|
||||
DECLARE VARIABLE REP_NUM VARCHAR(255);
|
||||
DECLARE VARIABLE REP_DATE TIMESTAMP;
|
||||
DECLARE VARIABLE CMT VARCHAR(4000);
|
||||
BEGIN
|
||||
EXECUTE PROCEDURE FTV_M2M_GET('WHOS') RETURNING_VALUES :WHOS;
|
||||
EXECUTE PROCEDURE FTV_M2M_GET('D_NUM') RETURNING_VALUES :ACC_D;
|
||||
EXECUTE PROCEDURE FTV_M2M_GET('K_NUM') RETURNING_VALUES :ACC_K;
|
||||
EXECUTE PROCEDURE FTV_M2M_GET('MARKET') RETURNING_VALUES :MARKET;
|
||||
EXECUTE PROCEDURE FTV_M2M_GET('B_TYPE') RETURNING_VALUES :B_TYPE;
|
||||
EXECUTE PROCEDURE FTV_M2M_GET('CORR_FACE') RETURNING_VALUES :CORR_FACE;
|
||||
EXECUTE PROCEDURE FTV_M2M_GET('FILER_CAT') RETURNING_VALUES :FILER_CAT;
|
||||
EXECUTE PROCEDURE FTV_M2M_GET('FILER_RUBRICA') RETURNING_VALUES :RUBRICA;
|
||||
|
||||
SELECT FIRST 1 H.DOC, D.NUM, D.D_DATE,
|
||||
CASE WHEN DC.SYS_NAME = 'I_CLISER_M2M_ZACH' THEN 'IN' WHEN DC.SYS_NAME = 'I_CLISER_M2M_SPIS' THEN 'OUT' END,
|
||||
D.D_DATE, D.NUM, F.SHARE, F.AMOUNT, F.CLIENT, H.DEPONENT, 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
|
||||
INTO :CLISER_ID, :CLISER_NUM, :CLISER_DATE, :DIRECTION, :DD_DATE, :DD_NUM, :SHARE, :AMOUNT, :CLIENT, :FACE, :PLACE;
|
||||
|
||||
IF (CLISER_ID IS NULL) THEN
|
||||
EXECUTE PROCEDURE FTV_M2M_FAIL('Не найдено M2M-поручение по GUID + REFERENCE');
|
||||
EXECUTE PROCEDURE FTV_M2M_DAY(:DD_DATE) RETURNING_VALUES :DD_DATE;
|
||||
|
||||
/* Направление */
|
||||
SELECT FIRST 1 DC.ID FROM OD_DOC_CATS DC
|
||||
WHERE DC.SYS_NAME = CASE WHEN :DIRECTION = 'IN' THEN 'OP_INV_ZACH' ELSE 'OP_INV_SPIS' END
|
||||
INTO :D_CAT;
|
||||
|
||||
/* ID и номер операции */
|
||||
OPER_ID = GEN_ID(OD_DOCS_ID_GEN, 1);
|
||||
EXECUTE PROCEDURE FU_NEXT_DOC_NUM (:D_CAT, :OPER_DATE, NULL, :OPER_ID) RETURNING_VALUES :NUM, :NEW_DATE;
|
||||
NUM = NUM || '_' || REFERENCE;
|
||||
|
||||
/* Документ */
|
||||
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);
|
||||
|
||||
/* Отчёт НРД из PARAMETERS — «<номер отчёта> от <дд.мм.гггг>»: номер — до первого пробела, дата — после
|
||||
последнего. Основание операции (B_NUM, B_DATE) и входящая запись — по отчёту НРД, а не по M2M-поручению
|
||||
(MSSQL-версия, правка программиста 24.10.2026). */
|
||||
P = TRIM(PARAMETERS);
|
||||
REP_NUM = LEFT(P, POSITION(' ' IN P || ' ') - 1);
|
||||
REP_DATE = CAST(RIGHT(P, POSITION(' ' IN REVERSE(P)) - 1) AS TIMESTAMP);
|
||||
|
||||
IF (DIRECTION = 'IN') THEN
|
||||
BEGIN
|
||||
CMT = 'Перевод M2M (без поручения), GUID ' || GUID || ', референс ' || REFERENCE;
|
||||
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, :ACC_D, :CLIENT, :ACC_K, :MARKET,
|
||||
NULL, NULL, NULL, :B_TYPE, :REP_DATE, :REP_NUM, :OPER_DATE, :OPER_DATE, :OPER_DATE, 0, :FACE, NULL, :CMT);
|
||||
END
|
||||
ELSE IF (DIRECTION = 'OUT') THEN
|
||||
BEGIN
|
||||
CMT = 'Перевод M2M, GUID ' || GUID || ', референс ' || REFERENCE;
|
||||
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, :ACC_D, :CLIENT, :ACC_K, :MARKET,
|
||||
NULL, NULL, NULL, :B_TYPE, :REP_DATE, :REP_NUM, :OPER_DATE, :OPER_DATE, :OPER_DATE, 0, :FACE, NULL, :CMT);
|
||||
END
|
||||
|
||||
/* Входящая запись (отчёт НРД) */
|
||||
FILER_ID = GEN_ID(OD_DOCS_ID_GEN, 1);
|
||||
FILER_DATE = CURRENT_TIMESTAMP;
|
||||
EXECUTE PROCEDURE FU_NEXT_DOC_NUM ('D_FILER', :FILER_DATE, 1, :FILER_ID) RETURNING_VALUES :FILER_NUM, :NEW_DATE;
|
||||
INSERT INTO OD_DOCS (ID, NUM, D_CAT, D_DATE, STATE, DIRECTION, WHOS)
|
||||
VALUES (:FILER_ID, :FILER_NUM, :FILER_CAT, :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, MESS)
|
||||
VALUES (:FILER_ID, 8, :RUBRICA, 'Отчет (перевод через MOEX-МОСТ)', :REP_NUM, :REP_DATE, :CORR_FACE,
|
||||
:FACE, :CLIENT, :PLACE, :FILER_DATE, NULL, 'MS010 / ' || :GUID || ' / ' || :REFERENCE);
|
||||
|
||||
/* Связь входящей записи с операцией */
|
||||
STEP_ID = GEN_ID(OD_STEPS_ID_GEN, 1);
|
||||
INSERT INTO OD_STEPS (ID, DOC, A_DOC, ERR_CODE, S_TYPE, OUT_STATE, S_DATE, OPER)
|
||||
VALUES (:STEP_ID, :FILER_ID, :OPER_ID, 2004, 0, 0, CURRENT_TIMESTAMP, NULL);
|
||||
|
||||
/* И сразу проводим */
|
||||
EXECUTE PROCEDURE FTV_M2M_PROCESS_WIRING (:OPER_ID);
|
||||
END^
|
||||
|
||||
|
||||
/* Обновление справочника участников сервиса: недостающие юрлица и коды M2M в Fancy, предупреждения о
|
||||
двойниках по ИНН и расхождении кодов. Возвращает текст отчёта (пишется в COMMENT события HANDBOOK). */
|
||||
CREATE OR ALTER PROCEDURE FTV_M2M_UPDATE_HANDBOOK RETURNS (CMT BLOB SUB_TYPE TEXT) AS
|
||||
DECLARE VARIABLE M2M_CODER INTEGER;
|
||||
DECLARE VARIABLE CNT INTEGER;
|
||||
DECLARE VARIABLE FACE_ID INTEGER;
|
||||
DECLARE VARIABLE HB_ID INTEGER;
|
||||
DECLARE VARIABLE RW INTEGER = 0;
|
||||
DECLARE VARIABLE INN VARCHAR(12);
|
||||
DECLARE VARIABLE CODE VARCHAR(64);
|
||||
DECLARE VARIABLE CODE2 VARCHAR(64);
|
||||
DECLARE VARIABLE NAME_SHORT VARCHAR(255);
|
||||
DECLARE VARIABLE NAME_FULL VARCHAR(255);
|
||||
DECLARE VARIABLE CRLF CHAR(2);
|
||||
BEGIN
|
||||
CRLF = ASCII_CHAR(13) || ASCII_CHAR(10);
|
||||
SELECT FIRST 1 F.SELF_ID FROM OD_FACES F WHERE F.LAST_FLAG = 1 AND F.MNEM = 'M2M' INTO :M2M_CODER;
|
||||
IF (M2M_CODER IS NULL) THEN
|
||||
EXECUTE PROCEDURE FTV_M2M_FAIL('В Fancy нет кодировщика M2M (OD_FACES с MNEM = ''M2M'')');
|
||||
|
||||
/* 1. Статистика по участникам */
|
||||
SELECT COUNT(*) FROM OD_FACE_CODERS C WHERE C.CODER = :M2M_CODER INTO :CNT;
|
||||
CMT = 'УЧАСТНИКИ СЕРВИСА: в справочнике юрлиц Фэнси - ' || CNT;
|
||||
SELECT COUNT(*) FROM FTV_M2M_HANDBOOK WHERE ACTIVE = 1 AND PARTICIPANT_CODE IS NOT NULL INTO :CNT;
|
||||
CMT = CMT || ', по данным НРД - ' || CNT;
|
||||
|
||||
/* 2. Принудительно добавляем отсутствующих юрлиц */
|
||||
FOR SELECT HB.ID, HB.PARTICIPANT_CODE, HB.INN, HB.NAME_SHORT, HB.NAME_FULL
|
||||
FROM FTV_M2M_HANDBOOK HB
|
||||
WHERE HB.ACTIVE = 1
|
||||
AND HB.INN NOT IN (SELECT F.INN FROM OD_FACES F WHERE F.LAST_FLAG = 1 AND F.E_DATE = '9999-01-01' AND F.INN IS NOT NULL)
|
||||
ORDER BY HB.NAME_SHORT
|
||||
INTO :HB_ID, :CODE, :INN, :NAME_SHORT, :NAME_FULL DO
|
||||
/* один ИНН бывает в справочнике дважды (СПБ Банк — место расчётов и депозитарий): второе юрлицо не создаём,
|
||||
код к созданному добавит шаг 3 (в MSSQL-версии — два одинаковых юрлица) */
|
||||
IF (NOT EXISTS (SELECT 1 FROM OD_FACES F WHERE F.INN = :INN AND F.LAST_FLAG = 1 AND F.E_DATE = '9999-01-01')) THEN
|
||||
BEGIN
|
||||
RW = RW + 1;
|
||||
FACE_ID = GEN_ID(OD_FACES_ID_GEN, 1);
|
||||
INSERT INTO OD_FACES (ID, S_TYPE, INN, NAME) VALUES (:FACE_ID, 1, :INN, :NAME_SHORT);
|
||||
INSERT INTO OD_U_FACES (FACE, FULL_NAME) VALUES (:FACE_ID, :NAME_FULL);
|
||||
/* у мест расчётов (KIND = 'PLACE') кода участника нет, а OD_FACE_CODERS.CODE — NOT NULL: в MSSQL-версии
|
||||
такой справочник падает целиком, если места расчётов ещё нет в юрлицах Fancy */
|
||||
IF (CODE IS NOT NULL) THEN
|
||||
INSERT INTO OD_FACE_CODERS (CODE, CODER, FACE) VALUES (:CODE, :M2M_CODER, :FACE_ID);
|
||||
CMT = CMT || CRLF || RW || '. Добавлено юрлицо: ' || COALESCE(CODE, '') || ', ' || INN || ', ' || COALESCE(NAME_SHORT, '');
|
||||
END
|
||||
|
||||
/* 3. Недостающие коды участников к существующим юрлицам, с предупреждением о двойниках по ИНН
|
||||
(в подзапросе ИНН без NULL: иначе NOT IN не находит ни одной строки) */
|
||||
FOR SELECT HB.ID, HB.PARTICIPANT_CODE, HB.INN, HB.NAME_SHORT, MIN(F.SELF_ID), COUNT(*)
|
||||
FROM FTV_M2M_HANDBOOK HB
|
||||
LEFT JOIN OD_FACES F ON F.INN = HB.INN
|
||||
LEFT JOIN OD_FACE_CODERS C ON C.CODER = :M2M_CODER AND F.SELF_ID = C.FACE
|
||||
WHERE HB.ACTIVE = 1
|
||||
AND HB.PARTICIPANT_CODE IS NOT NULL
|
||||
AND F.S_TYPE = 1
|
||||
AND F.LAST_FLAG = 1
|
||||
AND F.E_DATE = '9999-01-01'
|
||||
AND C.CODE IS NULL
|
||||
AND F.INN NOT IN (SELECT F2.INN FROM OD_FACE_CODERS C2
|
||||
JOIN OD_FACES F2 ON F2.SELF_ID = C2.FACE AND F2.LAST_FLAG = 1 AND F2.E_DATE = '9999-01-01'
|
||||
WHERE C2.CODER = :M2M_CODER AND F2.INN IS NOT NULL)
|
||||
GROUP BY HB.ID, HB.PARTICIPANT_CODE, HB.INN, HB.NAME_SHORT
|
||||
ORDER BY HB.NAME_SHORT
|
||||
INTO :HB_ID, :CODE, :INN, :NAME_SHORT, :FACE_ID, :CNT DO
|
||||
BEGIN
|
||||
RW = RW + 1;
|
||||
INSERT INTO OD_FACE_CODERS (CODE, CODER, FACE) VALUES (:CODE, :M2M_CODER, :FACE_ID);
|
||||
CMT = CMT || CRLF || RW || '. Добавлен код участника ' || CODE || ' к юрлицу: ' || INN || ', ' || COALESCE(NAME_SHORT, '');
|
||||
IF (CNT > 1) THEN
|
||||
CMT = CMT || ' (ПРЕДУПРЕЖДЕНИЕ: в Фэнси найдено несколько юрлиц с ИНН ' || INN || ')';
|
||||
END
|
||||
|
||||
IF (RW = 0) THEN
|
||||
CMT = CMT || ' (при обработке нового справочника НРД никаких изменений не обнаружено)';
|
||||
|
||||
/* 4. Отличающиеся коды в Fancy и НРД для одного юрлица (по ИНН) или лишние коды в Fancy */
|
||||
FOR SELECT F.INN, F.NAME, C.CODE, HB.PARTICIPANT_CODE
|
||||
FROM OD_FACE_CODERS C
|
||||
LEFT JOIN OD_FACES F ON F.SELF_ID = C.FACE AND F.LAST_FLAG = 1 AND F.E_DATE = '9999-01-01'
|
||||
LEFT JOIN FTV_M2M_HANDBOOK HB ON HB.INN = F.INN AND HB.ACTIVE = 1 AND HB.PARTICIPANT_CODE IS NOT NULL
|
||||
WHERE C.CODER = :M2M_CODER
|
||||
AND (C.CODE <> HB.PARTICIPANT_CODE OR HB.PARTICIPANT_CODE IS NULL)
|
||||
INTO :INN, :NAME_SHORT, :CODE, :CODE2 DO
|
||||
BEGIN
|
||||
RW = RW + 1;
|
||||
IF (CODE2 IS NULL) THEN
|
||||
CMT = CMT || CRLF || 'КРИТИЧЕСКОЕ ПРЕДУПРЕЖДЕНИЕ: для юрлица ' || COALESCE(INN, '?') || ' (' || COALESCE(NAME_SHORT, '') || ') в Фэнси указан код ' || CODE || ', отсутствующий в НРД';
|
||||
ELSE
|
||||
CMT = CMT || CRLF || 'КРИТИЧЕСКОЕ ПРЕДУПРЕЖДЕНИЕ: для юрлица ' || COALESCE(INN, '?') || ' (' || COALESCE(NAME_SHORT, '') || ') в Фэнси указан код ' || CODE || ', отличающийся от кода в НРД ' || CODE2;
|
||||
END
|
||||
END^
|
||||
|
||||
|
||||
/* Обработка одного события FTV_M2M_EVENTS. Ссылки — https://lic.zetit.ru/docs/fancy-events */
|
||||
CREATE OR ALTER PROCEDURE FTV_M2M_PROCESS_EVENTS (ID INTEGER) AS
|
||||
DECLARE VARIABLE GUID CHAR(36);
|
||||
DECLARE VARIABLE REFERENCE VARCHAR(255);
|
||||
DECLARE VARIABLE EVENT_TYPE VARCHAR(64);
|
||||
DECLARE VARIABLE EVENT_DATE TIMESTAMP;
|
||||
DECLARE VARIABLE M2M_REJECT_CODE VARCHAR(1000);
|
||||
DECLARE VARIABLE PARAMETERS VARCHAR(4000);
|
||||
DECLARE VARIABLE DIRECTION VARCHAR(64);
|
||||
DECLARE VARIABLE CMT BLOB SUB_TYPE TEXT;
|
||||
DECLARE VARIABLE SHARE INTEGER;
|
||||
DECLARE VARIABLE AMOUNT DOUBLE PRECISION;
|
||||
DECLARE VARIABLE PLACE INTEGER;
|
||||
DECLARE VARIABLE CLIENT INTEGER;
|
||||
DECLARE VARIABLE FACE INTEGER;
|
||||
DECLARE VARIABLE CLISER_ID INTEGER;
|
||||
DECLARE VARIABLE DOL_ID INTEGER;
|
||||
DECLARE VARIABLE DD_DATE TIMESTAMP;
|
||||
DECLARE VARIABLE DD_NUM VARCHAR(64);
|
||||
DECLARE VARIABLE OPER_ID INTEGER;
|
||||
DECLARE VARIABLE ST INTEGER;
|
||||
DECLARE VARIABLE EV_S VARCHAR(19);
|
||||
DECLARE VARIABLE NOW_S VARCHAR(19);
|
||||
BEGIN
|
||||
SELECT FIRST 1 ME.GUID, ME.REFERENCE, ME.EVENT_TYPE, ME.EVENT_DATE, ME.M2M_REJECT_CODE, ME.PARAMETERS, M.DIRECTION,
|
||||
H.DOC, F.DOL, D.D_DATE, D.NUM, F.SHARE, F.AMOUNT, F.PLACE, F.CLIENT, H.DEPONENT
|
||||
FROM FTV_M2M_EVENTS ME
|
||||
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
|
||||
INTO :GUID, :REFERENCE, :EVENT_TYPE, :EVENT_DATE, :M2M_REJECT_CODE, :PARAMETERS, :DIRECTION,
|
||||
:CLISER_ID, :DOL_ID, :DD_DATE, :DD_NUM, :SHARE, :AMOUNT, :PLACE, :CLIENT, :FACE;
|
||||
|
||||
/* 1. Обновление справочника участников сервиса */
|
||||
IF (EVENT_TYPE = 'HANDBOOK') THEN
|
||||
BEGIN
|
||||
EXECUTE PROCEDURE FTV_M2M_UPDATE_HANDBOOK RETURNING_VALUES :CMT;
|
||||
UPDATE FTV_M2M_EVENTS SET COMMENT = :CMT WHERE ID = :ID;
|
||||
EXIT;
|
||||
END
|
||||
|
||||
/* 1a. BJ_PING — проверочное событие bj (без GUID и REFERENCE): отметить обработанным, документ не трогать */
|
||||
IF (EVENT_TYPE = 'BJ_PING') THEN EXIT;
|
||||
|
||||
/* 2. Критические ошибки */
|
||||
IF (GUID IS NULL) THEN EXECUTE PROCEDURE FTV_M2M_FAIL('Не указан GUID');
|
||||
IF (REFERENCE IS NULL) THEN EXECUTE PROCEDURE FTV_M2M_FAIL('Не указан REFERENCE');
|
||||
IF (DOL_ID IS NULL) THEN EXECUTE PROCEDURE FTV_M2M_FAIL('Не найдена строка M2M-поручения по GUID + REFERENCE');
|
||||
|
||||
/* 3. Признак документа */
|
||||
EXECUTE PROCEDURE FTV_M2M_DT(:EVENT_DATE) RETURNING_VALUES :EV_S;
|
||||
EXECUTE PROCEDURE FTV_M2M_DT(CURRENT_TIMESTAMP) RETURNING_VALUES :NOW_S;
|
||||
INSERT INTO OD_INVALIDS (DOC, STATUS, COMMENT, DO_ING)
|
||||
VALUES (:CLISER_ID, 0,
|
||||
:EV_S || ' - ' || :EVENT_TYPE || COALESCE(' (референс - ' || :REFERENCE || ')', '') || COALESCE(' (параметры - ' || :PARAMETERS || ')', ''),
|
||||
'Время начала обработки: ' || :NOW_S);
|
||||
|
||||
/* 4. Обработка каждого типа события. ID = 0 в UPDATE OD_DOCS — служебное изменение Fancy (триггер оставляет
|
||||
прежний ID и не проверяет права оператора). */
|
||||
IF (EVENT_TYPE = 'BJ_RECEIVED') THEN
|
||||
UPDATE OD_DOCS SET ID = 0, STATE = 1 WHERE ID = :CLISER_ID; /* START — начало обработки */
|
||||
ELSE IF (EVENT_TYPE = 'BJ_SENT') THEN
|
||||
BEGIN
|
||||
ST = NULL;
|
||||
SELECT FIRST 1 S.ID FROM OD_DOC_STATES S WHERE S.SYS_NAME = 'WAIT_RESP_DECIS' INTO :ST;
|
||||
UPDATE OD_DOCS SET ID = 0, STATE = :ST WHERE ID = :CLISER_ID; /* ожидание ответа Response/Decision */
|
||||
END
|
||||
ELSE IF (EVENT_TYPE IN ('BJ_DECISION_CONFIRM', 'BJ_CP_CONFIRM')) THEN
|
||||
UPDATE OD_DOCS SET ID = 0, STATE = 8 WHERE ID = :CLISER_ID; /* RECIEVED — принят, ожидает обработки */
|
||||
ELSE IF (EVENT_TYPE IN ('BJ_DECISION_REJECT', 'BJ_CP_REJECT')) THEN
|
||||
UPDATE OD_DOCS SET ID = 0, STATE = 3 WHERE ID = :CLISER_ID; /* REJECT — отмена обработки */
|
||||
ELSE IF (EVENT_TYPE = 'BJ_SERVICE_ERROR') THEN
|
||||
UPDATE OD_DOCS SET ID = 0, STATE = 3 WHERE ID = :CLISER_ID;
|
||||
ELSE IF (EVENT_TYPE = 'BJ_ORDER_SENT') THEN
|
||||
BEGIN
|
||||
ST = NULL;
|
||||
SELECT FIRST 1 S.ID FROM OD_DOC_STATES S WHERE S.SYS_NAME = 'WAIT_OPER' INTO :ST;
|
||||
UPDATE OD_DOCS SET ID = 0, STATE = :ST WHERE ID = :CLISER_ID; /* ожидание исполнения операции */
|
||||
END
|
||||
ELSE IF (EVENT_TYPE = 'BJ_ORDER_ACCEPTED') THEN
|
||||
BEGIN
|
||||
EXECUTE PROCEDURE FTV_M2M_PROCESS_OPERATION (:GUID, :REFERENCE, :EVENT_DATE, :PARAMETERS) RETURNING_VALUES :OPER_ID;
|
||||
UPDATE OD_DOCS SET ID = 0, STATE = 2 WHERE ID = :CLISER_ID; /* FINAL — конец обработки */
|
||||
END
|
||||
ELSE IF (EVENT_TYPE = 'BJ_EXECUTED') THEN
|
||||
EXIT; /* уже исполнено по BJ_ORDER_ACCEPTED */
|
||||
ELSE IF (EVENT_TYPE = 'BJ_CANCELLED') THEN
|
||||
UPDATE OD_DOCS SET ID = 0, STATE = 3 WHERE ID = :CLISER_ID;
|
||||
ELSE IF (EVENT_TYPE = 'BJ_GATEWAY_REJECT') THEN
|
||||
UPDATE OD_DOCS SET ID = 0, STATE = 3 WHERE ID = :CLISER_ID;
|
||||
ELSE IF (EVENT_TYPE IN ('BJ_ORDER_LATE', 'BJ_QUIK_BLOCKED', 'BJ_QUIK_RELEASED', 'BJ_QUIK_SIGNED', 'BJ_QUIK_DEPOSITED')) THEN
|
||||
EXIT; /* ничего не делать */
|
||||
ELSE
|
||||
EXECUTE PROCEDURE FTV_M2M_FAIL('От сервиса BJ получено неизвестное событие ' || EVENT_TYPE);
|
||||
END^
|
||||
|
||||
|
||||
/* Создаёт M2M-поручение вместе с подвалом документа по записи FTV_M2M. */
|
||||
CREATE OR ALTER PROCEDURE FTV_M2M_PROCESS_CREATE (ID INTEGER) AS
|
||||
DECLARE VARIABLE WHOS INTEGER;
|
||||
DECLARE VARIABLE FILER_CAT INTEGER;
|
||||
DECLARE VARIABLE GUID CHAR(36);
|
||||
DECLARE VARIABLE DIRECTION VARCHAR(64);
|
||||
DECLARE VARIABLE CONTRAGENT_M2M_CODE VARCHAR(255);
|
||||
DECLARE VARIABLE DEPONENT_NAME VARCHAR(255);
|
||||
DECLARE VARIABLE DEPONENT_DOC VARCHAR(255);
|
||||
DECLARE VARIABLE REFERENCE VARCHAR(36);
|
||||
DECLARE VARIABLE ISIN VARCHAR(12);
|
||||
DECLARE VARIABLE SEC_CODE VARCHAR(255);
|
||||
DECLARE VARIABLE AMOUNT DOUBLE PRECISION;
|
||||
DECLARE VARIABLE PLACE_NUMBER VARCHAR(255);
|
||||
DECLARE VARIABLE LINK_NUMBER VARCHAR(128);
|
||||
DECLARE VARIABLE DEPO_NUMBER VARCHAR(128);
|
||||
DECLARE VARIABLE D_CAT INTEGER;
|
||||
DECLARE VARIABLE NUM VARCHAR(255);
|
||||
DECLARE VARIABLE NEW_DATE TIMESTAMP;
|
||||
DECLARE VARIABLE D_DATE TIMESTAMP;
|
||||
DECLARE VARIABLE CONTRAGENT INTEGER;
|
||||
DECLARE VARIABLE DEPONENT INTEGER;
|
||||
DECLARE VARIABLE SHARE INTEGER;
|
||||
DECLARE VARIABLE PLACE INTEGER;
|
||||
DECLARE VARIABLE DEPO INTEGER;
|
||||
DECLARE VARIABLE CLISER_ID INTEGER;
|
||||
DECLARE VARIABLE FILER_ID INTEGER;
|
||||
DECLARE VARIABLE FILER_NUM VARCHAR(255);
|
||||
DECLARE VARIABLE FILER_DATE TIMESTAMP;
|
||||
DECLARE VARIABLE DOL_ID INTEGER;
|
||||
DECLARE VARIABLE D_DAY TIMESTAMP;
|
||||
BEGIN
|
||||
EXECUTE PROCEDURE FTV_M2M_GET('WHOS') RETURNING_VALUES :WHOS;
|
||||
|
||||
SELECT M.GUID, M.DIRECTION, M.CONTRAGENT_M2M_CODE, M.DEPONENT_NAME, M.DEPONENT_DOC FROM FTV_M2M M WHERE M.ID = :ID
|
||||
INTO :GUID, :DIRECTION, :CONTRAGENT_M2M_CODE, :DEPONENT_NAME, :DEPONENT_DOC;
|
||||
|
||||
/* 1.1. Неуникальный GUID */
|
||||
IF (EXISTS (SELECT 1 FROM D_CLISER_M2M WHERE DOC_GUID = :GUID)) THEN
|
||||
EXECUTE PROCEDURE FTV_M2M_FAIL('Неуникальный GUID');
|
||||
|
||||
/* 1.2. Направление перевода */
|
||||
IF (DIRECTION = 'IN') THEN
|
||||
SELECT FIRST 1 C.ID FROM OD_DOC_CATS C WHERE C.SYS_NAME = 'I_CLISER_M2M_ZACH' INTO :D_CAT;
|
||||
ELSE IF (DIRECTION = 'OUT') THEN
|
||||
SELECT FIRST 1 C.ID FROM OD_DOC_CATS C WHERE C.SYS_NAME = 'I_CLISER_M2M_SPIS' INTO :D_CAT;
|
||||
ELSE
|
||||
EXECUTE PROCEDURE FTV_M2M_FAIL('Ошибочное направление DIRECTION (должно быть IN, OUT)');
|
||||
|
||||
/* 1.3. Контрагент */
|
||||
SELECT FIRST 1 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
|
||||
INTO :CONTRAGENT;
|
||||
IF (CONTRAGENT IS NULL) THEN
|
||||
EXECUTE PROCEDURE FTV_M2M_FAIL('Не найден депозитарий-контрагент');
|
||||
|
||||
/* 1.4. Депонент (как в MSSQL-версии: не найден — поручение создаётся без депонента) */
|
||||
SELECT FIRST 1 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, ' ', '')
|
||||
INTO :DEPONENT;
|
||||
|
||||
/* 1.5. Шапка документа */
|
||||
CLISER_ID = GEN_ID(OD_DOCS_ID_GEN, 1);
|
||||
D_DATE = CURRENT_TIMESTAMP;
|
||||
EXECUTE PROCEDURE FU_NEXT_DOC_NUM (:D_CAT, :D_DATE, NULL, :CLISER_ID) RETURNING_VALUES :NUM, :NEW_DATE;
|
||||
|
||||
INSERT INTO OD_DOCS (ID, NUM, PARAM, D_CAT, D_DATE, STATE, DIRECTION, WHOS, KVIT)
|
||||
VALUES (: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)
|
||||
VALUES (:CLISER_ID, :DEPONENT, :GUID, 0, 0, NULL, NULL, NULL);
|
||||
IF (DIRECTION = 'IN') THEN
|
||||
INSERT INTO OD_IN_DOCS (DOC, FROM_FACE, PASS_KIND) VALUES (:CLISER_ID, :CONTRAGENT, 8);
|
||||
ELSE
|
||||
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. Подвал документа */
|
||||
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
|
||||
INTO :REFERENCE, :ISIN, :SEC_CODE, :AMOUNT, :PLACE_NUMBER, :LINK_NUMBER, :DEPO_NUMBER DO
|
||||
BEGIN
|
||||
SHARE = NULL; PLACE = NULL; DEPO = NULL;
|
||||
|
||||
/* Бумага */
|
||||
SELECT FIRST 1 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
|
||||
INTO :SHARE;
|
||||
|
||||
/* Счёт места хранения */
|
||||
SELECT FIRST 1 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
|
||||
INTO :PLACE;
|
||||
|
||||
/* Счёт клиента */
|
||||
SELECT FIRST 1 C.ID FROM OD_CLIENTS C WHERE C.NUMBER = :DEPO_NUMBER INTO :DEPO;
|
||||
|
||||
DOL_ID = GEN_ID(OD_DOLS_ID_GEN, 1);
|
||||
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);
|
||||
|
||||
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
|
||||
|
||||
/* 1.8. Входящая запись — только для списания (исходящий перевод OUT) */
|
||||
IF (DIRECTION = 'OUT') THEN
|
||||
BEGIN
|
||||
EXECUTE PROCEDURE FTV_M2M_GET('FILER_CAT') RETURNING_VALUES :FILER_CAT;
|
||||
FILER_ID = GEN_ID(OD_DOCS_ID_GEN, 1);
|
||||
FILER_DATE = CURRENT_TIMESTAMP;
|
||||
EXECUTE PROCEDURE FU_NEXT_DOC_NUM ('D_FILER', :FILER_DATE, 1, :FILER_ID) RETURNING_VALUES :FILER_NUM, :NEW_DATE;
|
||||
EXECUTE PROCEDURE FTV_M2M_DAY(:D_DATE) RETURNING_VALUES :D_DAY;
|
||||
INSERT INTO OD_DOCS (ID, NUM, D_CAT, D_DATE, STATE, DIRECTION, WHOS)
|
||||
VALUES (:FILER_ID, :FILER_NUM, :FILER_CAT, :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, MESS)
|
||||
VALUES (:FILER_ID, 7, 8, 'M2M поручение на списание через MOEX-МОСТ', :NUM, :D_DAY, :DEPONENT, :DEPONENT,
|
||||
:DEPO, :PLACE, :FILER_DATE, :D_CAT, :GUID);
|
||||
/* Связываем с M2M-поручением */
|
||||
UPDATE OD_DOCS SET ID = 0, KVIT = :FILER_ID WHERE ID = :CLISER_ID;
|
||||
END
|
||||
END^
|
||||
|
||||
|
||||
/* Запуск обработки: новые поручения (FTV_M2M) и события (FTV_M2M_EVENTS) со STATE = 'TODO'. Вызывает bj
|
||||
(в Firebird нет SQL Server Agent). Ошибка одной записи не останавливает остальные. */
|
||||
CREATE OR ALTER PROCEDURE FTV_M2M_JOB AS
|
||||
DECLARE VARIABLE ID INTEGER;
|
||||
DECLARE VARIABLE ST VARCHAR(64);
|
||||
DECLARE VARIABLE CMT VARCHAR(1000);
|
||||
DECLARE VARIABLE R INTEGER;
|
||||
BEGIN
|
||||
/* НОВЫЕ ПОРУЧЕНИЯ */
|
||||
FOR SELECT M.ID FROM FTV_M2M M WHERE M.STATE = 'TODO' ORDER BY M.TIMENEW, M.ID INTO :ID DO
|
||||
BEGIN
|
||||
UPDATE FTV_M2M SET TIMEBEG = CURRENT_TIMESTAMP WHERE ID = :ID;
|
||||
ST = 'SUCCESS'; CMT = NULL;
|
||||
R = RDB$SET_CONTEXT('USER_TRANSACTION', 'FTV_M2M_ERR', NULL);
|
||||
BEGIN
|
||||
EXECUTE PROCEDURE FTV_M2M_PROCESS_CREATE (:ID);
|
||||
WHEN ANY DO
|
||||
BEGIN
|
||||
ST = 'ERROR';
|
||||
CMT = COALESCE(CAST(RDB$GET_CONTEXT('USER_TRANSACTION', 'FTV_M2M_ERR') AS VARCHAR(255) CHARACTER SET WIN1251), 'ошибка Fancy') || ' // FTV_M2M_PROCESS_CREATE // SQLCODE ' || SQLCODE || ', GDSCODE ' || GDSCODE;
|
||||
END
|
||||
END
|
||||
UPDATE FTV_M2M SET STATE = :ST, TIMEEND = CURRENT_TIMESTAMP, COMMENT = :CMT WHERE ID = :ID;
|
||||
END
|
||||
|
||||
/* НОВЫЕ СОБЫТИЯ */
|
||||
FOR SELECT E.ID FROM FTV_M2M_EVENTS E WHERE E.STATE = 'TODO' ORDER BY E.TIMENEW, E.ID INTO :ID DO
|
||||
BEGIN
|
||||
UPDATE FTV_M2M_EVENTS SET TIMEBEG = CURRENT_TIMESTAMP WHERE ID = :ID;
|
||||
ST = 'SUCCESS'; CMT = NULL;
|
||||
R = RDB$SET_CONTEXT('USER_TRANSACTION', 'FTV_M2M_ERR', NULL);
|
||||
BEGIN
|
||||
EXECUTE PROCEDURE FTV_M2M_PROCESS_EVENTS (:ID);
|
||||
WHEN ANY DO
|
||||
BEGIN
|
||||
ST = 'ERROR';
|
||||
CMT = COALESCE(CAST(RDB$GET_CONTEXT('USER_TRANSACTION', 'FTV_M2M_ERR') AS VARCHAR(255) CHARACTER SET WIN1251), 'ошибка Fancy') || ' // FTV_M2M_PROCESS_EVENTS // SQLCODE ' || SQLCODE || ', GDSCODE ' || GDSCODE;
|
||||
END
|
||||
END
|
||||
/* без ошибки COMMENT не трогаем: в нём отчёт справочника (HANDBOOK) */
|
||||
IF (CMT IS NOT NULL) THEN
|
||||
UPDATE FTV_M2M_EVENTS SET STATE = :ST, TIMEEND = CURRENT_TIMESTAMP, COMMENT = :CMT WHERE ID = :ID;
|
||||
ELSE
|
||||
UPDATE FTV_M2M_EVENTS SET STATE = :ST, TIMEEND = CURRENT_TIMESTAMP WHERE ID = :ID;
|
||||
END
|
||||
END^
|
||||
|
||||
SET TERM ; ^
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,354 @@
|
||||
/* Bridge&Join — права для процедур обработки M2M (Firebird 2.5).
|
||||
Процедуры работают правами, выданными им самим: логину bj нужно только EXECUTE на FTV_M2M_JOB и доступ к
|
||||
таблицам обмена FTV_M2M*. Выполнять под SYSDBA или владельцем базы. :BJ_USER — логин bj (подставляет bj). */
|
||||
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M TO PROCEDURE FTV_M2M_FAIL;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M_LINES TO PROCEDURE FTV_M2M_FAIL;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M_EVENTS TO PROCEDURE FTV_M2M_FAIL;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M_HANDBOOK TO PROCEDURE FTV_M2M_FAIL;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M_SETTINGS TO PROCEDURE FTV_M2M_FAIL;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_DOCS TO PROCEDURE FTV_M2M_FAIL;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_FACES TO PROCEDURE FTV_M2M_FAIL;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_FACE_CODERS TO PROCEDURE FTV_M2M_FAIL;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_U_FACES TO PROCEDURE FTV_M2M_FAIL;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_DOLS TO PROCEDURE FTV_M2M_FAIL;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_STEPS TO PROCEDURE FTV_M2M_FAIL;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_IN_DOCS TO PROCEDURE FTV_M2M_FAIL;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_OUT_DOCS TO PROCEDURE FTV_M2M_FAIL;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_INVALIDS TO PROCEDURE FTV_M2M_FAIL;
|
||||
GRANT SELECT, INSERT, UPDATE ON D_CLISER_M2M TO PROCEDURE FTV_M2M_FAIL;
|
||||
GRANT SELECT, INSERT, UPDATE ON D_CLISER_M2M_DOL TO PROCEDURE FTV_M2M_FAIL;
|
||||
GRANT SELECT, INSERT, UPDATE ON D_FILER TO PROCEDURE FTV_M2M_FAIL;
|
||||
GRANT SELECT, INSERT, UPDATE ON U_OP_INV_ZACH TO PROCEDURE FTV_M2M_FAIL;
|
||||
GRANT SELECT, INSERT, UPDATE ON U_OP_INV_SPIS TO PROCEDURE FTV_M2M_FAIL;
|
||||
GRANT SELECT ON OD_CLIENTS TO PROCEDURE FTV_M2M_FAIL;
|
||||
GRANT SELECT ON OD_CLIENTS_REC TO PROCEDURE FTV_M2M_FAIL;
|
||||
GRANT SELECT ON OD_DOC_CATS TO PROCEDURE FTV_M2M_FAIL;
|
||||
GRANT SELECT ON OD_DOC_STATES TO PROCEDURE FTV_M2M_FAIL;
|
||||
GRANT SELECT ON OD_VALUES TO PROCEDURE FTV_M2M_FAIL;
|
||||
GRANT SELECT ON OD_VALUE_CODERS TO PROCEDURE FTV_M2M_FAIL;
|
||||
GRANT SELECT ON OD_SYS_TABS TO PROCEDURE FTV_M2M_FAIL;
|
||||
GRANT SELECT ON OD_STEP_TYPES TO PROCEDURE FTV_M2M_FAIL;
|
||||
GRANT EXECUTE ON PROCEDURE FU_NEXT_DOC_NUM TO PROCEDURE FTV_M2M_FAIL;
|
||||
GRANT EXECUTE ON PROCEDURE OD_PR_INS_WIRS TO PROCEDURE FTV_M2M_FAIL;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_GET TO PROCEDURE FTV_M2M_FAIL;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_DT TO PROCEDURE FTV_M2M_FAIL;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_DAY TO PROCEDURE FTV_M2M_FAIL;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_PROCESS_WIRING TO PROCEDURE FTV_M2M_FAIL;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_PROCESS_OPERATION TO PROCEDURE FTV_M2M_FAIL;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_UPDATE_HANDBOOK TO PROCEDURE FTV_M2M_FAIL;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_PROCESS_EVENTS TO PROCEDURE FTV_M2M_FAIL;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_PROCESS_CREATE TO PROCEDURE FTV_M2M_FAIL;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_JOB TO PROCEDURE FTV_M2M_FAIL;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M TO PROCEDURE FTV_M2M_GET;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M_LINES TO PROCEDURE FTV_M2M_GET;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M_EVENTS TO PROCEDURE FTV_M2M_GET;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M_HANDBOOK TO PROCEDURE FTV_M2M_GET;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M_SETTINGS TO PROCEDURE FTV_M2M_GET;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_DOCS TO PROCEDURE FTV_M2M_GET;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_FACES TO PROCEDURE FTV_M2M_GET;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_FACE_CODERS TO PROCEDURE FTV_M2M_GET;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_U_FACES TO PROCEDURE FTV_M2M_GET;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_DOLS TO PROCEDURE FTV_M2M_GET;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_STEPS TO PROCEDURE FTV_M2M_GET;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_IN_DOCS TO PROCEDURE FTV_M2M_GET;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_OUT_DOCS TO PROCEDURE FTV_M2M_GET;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_INVALIDS TO PROCEDURE FTV_M2M_GET;
|
||||
GRANT SELECT, INSERT, UPDATE ON D_CLISER_M2M TO PROCEDURE FTV_M2M_GET;
|
||||
GRANT SELECT, INSERT, UPDATE ON D_CLISER_M2M_DOL TO PROCEDURE FTV_M2M_GET;
|
||||
GRANT SELECT, INSERT, UPDATE ON D_FILER TO PROCEDURE FTV_M2M_GET;
|
||||
GRANT SELECT, INSERT, UPDATE ON U_OP_INV_ZACH TO PROCEDURE FTV_M2M_GET;
|
||||
GRANT SELECT, INSERT, UPDATE ON U_OP_INV_SPIS TO PROCEDURE FTV_M2M_GET;
|
||||
GRANT SELECT ON OD_CLIENTS TO PROCEDURE FTV_M2M_GET;
|
||||
GRANT SELECT ON OD_CLIENTS_REC TO PROCEDURE FTV_M2M_GET;
|
||||
GRANT SELECT ON OD_DOC_CATS TO PROCEDURE FTV_M2M_GET;
|
||||
GRANT SELECT ON OD_DOC_STATES TO PROCEDURE FTV_M2M_GET;
|
||||
GRANT SELECT ON OD_VALUES TO PROCEDURE FTV_M2M_GET;
|
||||
GRANT SELECT ON OD_VALUE_CODERS TO PROCEDURE FTV_M2M_GET;
|
||||
GRANT SELECT ON OD_SYS_TABS TO PROCEDURE FTV_M2M_GET;
|
||||
GRANT SELECT ON OD_STEP_TYPES TO PROCEDURE FTV_M2M_GET;
|
||||
GRANT EXECUTE ON PROCEDURE FU_NEXT_DOC_NUM TO PROCEDURE FTV_M2M_GET;
|
||||
GRANT EXECUTE ON PROCEDURE OD_PR_INS_WIRS TO PROCEDURE FTV_M2M_GET;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_FAIL TO PROCEDURE FTV_M2M_GET;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_DT TO PROCEDURE FTV_M2M_GET;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_DAY TO PROCEDURE FTV_M2M_GET;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_PROCESS_WIRING TO PROCEDURE FTV_M2M_GET;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_PROCESS_OPERATION TO PROCEDURE FTV_M2M_GET;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_UPDATE_HANDBOOK TO PROCEDURE FTV_M2M_GET;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_PROCESS_EVENTS TO PROCEDURE FTV_M2M_GET;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_PROCESS_CREATE TO PROCEDURE FTV_M2M_GET;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_JOB TO PROCEDURE FTV_M2M_GET;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M TO PROCEDURE FTV_M2M_DT;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M_LINES TO PROCEDURE FTV_M2M_DT;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M_EVENTS TO PROCEDURE FTV_M2M_DT;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M_HANDBOOK TO PROCEDURE FTV_M2M_DT;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M_SETTINGS TO PROCEDURE FTV_M2M_DT;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_DOCS TO PROCEDURE FTV_M2M_DT;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_FACES TO PROCEDURE FTV_M2M_DT;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_FACE_CODERS TO PROCEDURE FTV_M2M_DT;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_U_FACES TO PROCEDURE FTV_M2M_DT;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_DOLS TO PROCEDURE FTV_M2M_DT;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_STEPS TO PROCEDURE FTV_M2M_DT;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_IN_DOCS TO PROCEDURE FTV_M2M_DT;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_OUT_DOCS TO PROCEDURE FTV_M2M_DT;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_INVALIDS TO PROCEDURE FTV_M2M_DT;
|
||||
GRANT SELECT, INSERT, UPDATE ON D_CLISER_M2M TO PROCEDURE FTV_M2M_DT;
|
||||
GRANT SELECT, INSERT, UPDATE ON D_CLISER_M2M_DOL TO PROCEDURE FTV_M2M_DT;
|
||||
GRANT SELECT, INSERT, UPDATE ON D_FILER TO PROCEDURE FTV_M2M_DT;
|
||||
GRANT SELECT, INSERT, UPDATE ON U_OP_INV_ZACH TO PROCEDURE FTV_M2M_DT;
|
||||
GRANT SELECT, INSERT, UPDATE ON U_OP_INV_SPIS TO PROCEDURE FTV_M2M_DT;
|
||||
GRANT SELECT ON OD_CLIENTS TO PROCEDURE FTV_M2M_DT;
|
||||
GRANT SELECT ON OD_CLIENTS_REC TO PROCEDURE FTV_M2M_DT;
|
||||
GRANT SELECT ON OD_DOC_CATS TO PROCEDURE FTV_M2M_DT;
|
||||
GRANT SELECT ON OD_DOC_STATES TO PROCEDURE FTV_M2M_DT;
|
||||
GRANT SELECT ON OD_VALUES TO PROCEDURE FTV_M2M_DT;
|
||||
GRANT SELECT ON OD_VALUE_CODERS TO PROCEDURE FTV_M2M_DT;
|
||||
GRANT SELECT ON OD_SYS_TABS TO PROCEDURE FTV_M2M_DT;
|
||||
GRANT SELECT ON OD_STEP_TYPES TO PROCEDURE FTV_M2M_DT;
|
||||
GRANT EXECUTE ON PROCEDURE FU_NEXT_DOC_NUM TO PROCEDURE FTV_M2M_DT;
|
||||
GRANT EXECUTE ON PROCEDURE OD_PR_INS_WIRS TO PROCEDURE FTV_M2M_DT;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_FAIL TO PROCEDURE FTV_M2M_DT;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_GET TO PROCEDURE FTV_M2M_DT;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_PROCESS_WIRING TO PROCEDURE FTV_M2M_DT;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_PROCESS_OPERATION TO PROCEDURE FTV_M2M_DT;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_UPDATE_HANDBOOK TO PROCEDURE FTV_M2M_DT;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_PROCESS_EVENTS TO PROCEDURE FTV_M2M_DT;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_PROCESS_CREATE TO PROCEDURE FTV_M2M_DT;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_JOB TO PROCEDURE FTV_M2M_DT;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M TO PROCEDURE FTV_M2M_PROCESS_WIRING;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M_LINES TO PROCEDURE FTV_M2M_PROCESS_WIRING;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M_EVENTS TO PROCEDURE FTV_M2M_PROCESS_WIRING;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M_HANDBOOK TO PROCEDURE FTV_M2M_PROCESS_WIRING;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M_SETTINGS TO PROCEDURE FTV_M2M_PROCESS_WIRING;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_DOCS TO PROCEDURE FTV_M2M_PROCESS_WIRING;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_FACES TO PROCEDURE FTV_M2M_PROCESS_WIRING;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_FACE_CODERS TO PROCEDURE FTV_M2M_PROCESS_WIRING;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_U_FACES TO PROCEDURE FTV_M2M_PROCESS_WIRING;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_DOLS TO PROCEDURE FTV_M2M_PROCESS_WIRING;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_STEPS TO PROCEDURE FTV_M2M_PROCESS_WIRING;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_IN_DOCS TO PROCEDURE FTV_M2M_PROCESS_WIRING;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_OUT_DOCS TO PROCEDURE FTV_M2M_PROCESS_WIRING;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_INVALIDS TO PROCEDURE FTV_M2M_PROCESS_WIRING;
|
||||
GRANT SELECT, INSERT, UPDATE ON D_CLISER_M2M TO PROCEDURE FTV_M2M_PROCESS_WIRING;
|
||||
GRANT SELECT, INSERT, UPDATE ON D_CLISER_M2M_DOL TO PROCEDURE FTV_M2M_PROCESS_WIRING;
|
||||
GRANT SELECT, INSERT, UPDATE ON D_FILER TO PROCEDURE FTV_M2M_PROCESS_WIRING;
|
||||
GRANT SELECT, INSERT, UPDATE ON U_OP_INV_ZACH TO PROCEDURE FTV_M2M_PROCESS_WIRING;
|
||||
GRANT SELECT, INSERT, UPDATE ON U_OP_INV_SPIS TO PROCEDURE FTV_M2M_PROCESS_WIRING;
|
||||
GRANT SELECT ON OD_CLIENTS TO PROCEDURE FTV_M2M_PROCESS_WIRING;
|
||||
GRANT SELECT ON OD_CLIENTS_REC TO PROCEDURE FTV_M2M_PROCESS_WIRING;
|
||||
GRANT SELECT ON OD_DOC_CATS TO PROCEDURE FTV_M2M_PROCESS_WIRING;
|
||||
GRANT SELECT ON OD_DOC_STATES TO PROCEDURE FTV_M2M_PROCESS_WIRING;
|
||||
GRANT SELECT ON OD_VALUES TO PROCEDURE FTV_M2M_PROCESS_WIRING;
|
||||
GRANT SELECT ON OD_VALUE_CODERS TO PROCEDURE FTV_M2M_PROCESS_WIRING;
|
||||
GRANT SELECT ON OD_SYS_TABS TO PROCEDURE FTV_M2M_PROCESS_WIRING;
|
||||
GRANT SELECT ON OD_STEP_TYPES TO PROCEDURE FTV_M2M_PROCESS_WIRING;
|
||||
GRANT EXECUTE ON PROCEDURE FU_NEXT_DOC_NUM TO PROCEDURE FTV_M2M_PROCESS_WIRING;
|
||||
GRANT EXECUTE ON PROCEDURE OD_PR_INS_WIRS TO PROCEDURE FTV_M2M_PROCESS_WIRING;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_FAIL TO PROCEDURE FTV_M2M_PROCESS_WIRING;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_GET TO PROCEDURE FTV_M2M_PROCESS_WIRING;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_DT TO PROCEDURE FTV_M2M_PROCESS_WIRING;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_DAY TO PROCEDURE FTV_M2M_PROCESS_WIRING;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_PROCESS_OPERATION TO PROCEDURE FTV_M2M_PROCESS_WIRING;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_UPDATE_HANDBOOK TO PROCEDURE FTV_M2M_PROCESS_WIRING;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_PROCESS_EVENTS TO PROCEDURE FTV_M2M_PROCESS_WIRING;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_PROCESS_CREATE TO PROCEDURE FTV_M2M_PROCESS_WIRING;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_JOB TO PROCEDURE FTV_M2M_PROCESS_WIRING;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M TO PROCEDURE FTV_M2M_PROCESS_OPERATION;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M_LINES TO PROCEDURE FTV_M2M_PROCESS_OPERATION;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M_EVENTS TO PROCEDURE FTV_M2M_PROCESS_OPERATION;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M_HANDBOOK TO PROCEDURE FTV_M2M_PROCESS_OPERATION;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M_SETTINGS TO PROCEDURE FTV_M2M_PROCESS_OPERATION;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_DOCS TO PROCEDURE FTV_M2M_PROCESS_OPERATION;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_FACES TO PROCEDURE FTV_M2M_PROCESS_OPERATION;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_FACE_CODERS TO PROCEDURE FTV_M2M_PROCESS_OPERATION;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_U_FACES TO PROCEDURE FTV_M2M_PROCESS_OPERATION;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_DOLS TO PROCEDURE FTV_M2M_PROCESS_OPERATION;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_STEPS TO PROCEDURE FTV_M2M_PROCESS_OPERATION;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_IN_DOCS TO PROCEDURE FTV_M2M_PROCESS_OPERATION;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_OUT_DOCS TO PROCEDURE FTV_M2M_PROCESS_OPERATION;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_INVALIDS TO PROCEDURE FTV_M2M_PROCESS_OPERATION;
|
||||
GRANT SELECT, INSERT, UPDATE ON D_CLISER_M2M TO PROCEDURE FTV_M2M_PROCESS_OPERATION;
|
||||
GRANT SELECT, INSERT, UPDATE ON D_CLISER_M2M_DOL TO PROCEDURE FTV_M2M_PROCESS_OPERATION;
|
||||
GRANT SELECT, INSERT, UPDATE ON D_FILER TO PROCEDURE FTV_M2M_PROCESS_OPERATION;
|
||||
GRANT SELECT, INSERT, UPDATE ON U_OP_INV_ZACH TO PROCEDURE FTV_M2M_PROCESS_OPERATION;
|
||||
GRANT SELECT, INSERT, UPDATE ON U_OP_INV_SPIS TO PROCEDURE FTV_M2M_PROCESS_OPERATION;
|
||||
GRANT SELECT ON OD_CLIENTS TO PROCEDURE FTV_M2M_PROCESS_OPERATION;
|
||||
GRANT SELECT ON OD_CLIENTS_REC TO PROCEDURE FTV_M2M_PROCESS_OPERATION;
|
||||
GRANT SELECT ON OD_DOC_CATS TO PROCEDURE FTV_M2M_PROCESS_OPERATION;
|
||||
GRANT SELECT ON OD_DOC_STATES TO PROCEDURE FTV_M2M_PROCESS_OPERATION;
|
||||
GRANT SELECT ON OD_VALUES TO PROCEDURE FTV_M2M_PROCESS_OPERATION;
|
||||
GRANT SELECT ON OD_VALUE_CODERS TO PROCEDURE FTV_M2M_PROCESS_OPERATION;
|
||||
GRANT SELECT ON OD_SYS_TABS TO PROCEDURE FTV_M2M_PROCESS_OPERATION;
|
||||
GRANT SELECT ON OD_STEP_TYPES TO PROCEDURE FTV_M2M_PROCESS_OPERATION;
|
||||
GRANT EXECUTE ON PROCEDURE FU_NEXT_DOC_NUM TO PROCEDURE FTV_M2M_PROCESS_OPERATION;
|
||||
GRANT EXECUTE ON PROCEDURE OD_PR_INS_WIRS TO PROCEDURE FTV_M2M_PROCESS_OPERATION;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_FAIL TO PROCEDURE FTV_M2M_PROCESS_OPERATION;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_GET TO PROCEDURE FTV_M2M_PROCESS_OPERATION;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_DT TO PROCEDURE FTV_M2M_PROCESS_OPERATION;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_DAY TO PROCEDURE FTV_M2M_PROCESS_OPERATION;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_PROCESS_WIRING TO PROCEDURE FTV_M2M_PROCESS_OPERATION;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_UPDATE_HANDBOOK TO PROCEDURE FTV_M2M_PROCESS_OPERATION;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_PROCESS_EVENTS TO PROCEDURE FTV_M2M_PROCESS_OPERATION;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_PROCESS_CREATE TO PROCEDURE FTV_M2M_PROCESS_OPERATION;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_JOB TO PROCEDURE FTV_M2M_PROCESS_OPERATION;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M TO PROCEDURE FTV_M2M_UPDATE_HANDBOOK;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M_LINES TO PROCEDURE FTV_M2M_UPDATE_HANDBOOK;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M_EVENTS TO PROCEDURE FTV_M2M_UPDATE_HANDBOOK;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M_HANDBOOK TO PROCEDURE FTV_M2M_UPDATE_HANDBOOK;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M_SETTINGS TO PROCEDURE FTV_M2M_UPDATE_HANDBOOK;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_DOCS TO PROCEDURE FTV_M2M_UPDATE_HANDBOOK;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_FACES TO PROCEDURE FTV_M2M_UPDATE_HANDBOOK;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_FACE_CODERS TO PROCEDURE FTV_M2M_UPDATE_HANDBOOK;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_U_FACES TO PROCEDURE FTV_M2M_UPDATE_HANDBOOK;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_DOLS TO PROCEDURE FTV_M2M_UPDATE_HANDBOOK;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_STEPS TO PROCEDURE FTV_M2M_UPDATE_HANDBOOK;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_IN_DOCS TO PROCEDURE FTV_M2M_UPDATE_HANDBOOK;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_OUT_DOCS TO PROCEDURE FTV_M2M_UPDATE_HANDBOOK;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_INVALIDS TO PROCEDURE FTV_M2M_UPDATE_HANDBOOK;
|
||||
GRANT SELECT, INSERT, UPDATE ON D_CLISER_M2M TO PROCEDURE FTV_M2M_UPDATE_HANDBOOK;
|
||||
GRANT SELECT, INSERT, UPDATE ON D_CLISER_M2M_DOL TO PROCEDURE FTV_M2M_UPDATE_HANDBOOK;
|
||||
GRANT SELECT, INSERT, UPDATE ON D_FILER TO PROCEDURE FTV_M2M_UPDATE_HANDBOOK;
|
||||
GRANT SELECT, INSERT, UPDATE ON U_OP_INV_ZACH TO PROCEDURE FTV_M2M_UPDATE_HANDBOOK;
|
||||
GRANT SELECT, INSERT, UPDATE ON U_OP_INV_SPIS TO PROCEDURE FTV_M2M_UPDATE_HANDBOOK;
|
||||
GRANT SELECT ON OD_CLIENTS TO PROCEDURE FTV_M2M_UPDATE_HANDBOOK;
|
||||
GRANT SELECT ON OD_CLIENTS_REC TO PROCEDURE FTV_M2M_UPDATE_HANDBOOK;
|
||||
GRANT SELECT ON OD_DOC_CATS TO PROCEDURE FTV_M2M_UPDATE_HANDBOOK;
|
||||
GRANT SELECT ON OD_DOC_STATES TO PROCEDURE FTV_M2M_UPDATE_HANDBOOK;
|
||||
GRANT SELECT ON OD_VALUES TO PROCEDURE FTV_M2M_UPDATE_HANDBOOK;
|
||||
GRANT SELECT ON OD_VALUE_CODERS TO PROCEDURE FTV_M2M_UPDATE_HANDBOOK;
|
||||
GRANT SELECT ON OD_SYS_TABS TO PROCEDURE FTV_M2M_UPDATE_HANDBOOK;
|
||||
GRANT SELECT ON OD_STEP_TYPES TO PROCEDURE FTV_M2M_UPDATE_HANDBOOK;
|
||||
GRANT EXECUTE ON PROCEDURE FU_NEXT_DOC_NUM TO PROCEDURE FTV_M2M_UPDATE_HANDBOOK;
|
||||
GRANT EXECUTE ON PROCEDURE OD_PR_INS_WIRS TO PROCEDURE FTV_M2M_UPDATE_HANDBOOK;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_FAIL TO PROCEDURE FTV_M2M_UPDATE_HANDBOOK;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_GET TO PROCEDURE FTV_M2M_UPDATE_HANDBOOK;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_DT TO PROCEDURE FTV_M2M_UPDATE_HANDBOOK;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_DAY TO PROCEDURE FTV_M2M_UPDATE_HANDBOOK;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_PROCESS_WIRING TO PROCEDURE FTV_M2M_UPDATE_HANDBOOK;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_PROCESS_OPERATION TO PROCEDURE FTV_M2M_UPDATE_HANDBOOK;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_PROCESS_EVENTS TO PROCEDURE FTV_M2M_UPDATE_HANDBOOK;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_PROCESS_CREATE TO PROCEDURE FTV_M2M_UPDATE_HANDBOOK;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_JOB TO PROCEDURE FTV_M2M_UPDATE_HANDBOOK;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M TO PROCEDURE FTV_M2M_PROCESS_EVENTS;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M_LINES TO PROCEDURE FTV_M2M_PROCESS_EVENTS;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M_EVENTS TO PROCEDURE FTV_M2M_PROCESS_EVENTS;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M_HANDBOOK TO PROCEDURE FTV_M2M_PROCESS_EVENTS;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M_SETTINGS TO PROCEDURE FTV_M2M_PROCESS_EVENTS;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_DOCS TO PROCEDURE FTV_M2M_PROCESS_EVENTS;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_FACES TO PROCEDURE FTV_M2M_PROCESS_EVENTS;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_FACE_CODERS TO PROCEDURE FTV_M2M_PROCESS_EVENTS;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_U_FACES TO PROCEDURE FTV_M2M_PROCESS_EVENTS;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_DOLS TO PROCEDURE FTV_M2M_PROCESS_EVENTS;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_STEPS TO PROCEDURE FTV_M2M_PROCESS_EVENTS;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_IN_DOCS TO PROCEDURE FTV_M2M_PROCESS_EVENTS;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_OUT_DOCS TO PROCEDURE FTV_M2M_PROCESS_EVENTS;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_INVALIDS TO PROCEDURE FTV_M2M_PROCESS_EVENTS;
|
||||
GRANT SELECT, INSERT, UPDATE ON D_CLISER_M2M TO PROCEDURE FTV_M2M_PROCESS_EVENTS;
|
||||
GRANT SELECT, INSERT, UPDATE ON D_CLISER_M2M_DOL TO PROCEDURE FTV_M2M_PROCESS_EVENTS;
|
||||
GRANT SELECT, INSERT, UPDATE ON D_FILER TO PROCEDURE FTV_M2M_PROCESS_EVENTS;
|
||||
GRANT SELECT, INSERT, UPDATE ON U_OP_INV_ZACH TO PROCEDURE FTV_M2M_PROCESS_EVENTS;
|
||||
GRANT SELECT, INSERT, UPDATE ON U_OP_INV_SPIS TO PROCEDURE FTV_M2M_PROCESS_EVENTS;
|
||||
GRANT SELECT ON OD_CLIENTS TO PROCEDURE FTV_M2M_PROCESS_EVENTS;
|
||||
GRANT SELECT ON OD_CLIENTS_REC TO PROCEDURE FTV_M2M_PROCESS_EVENTS;
|
||||
GRANT SELECT ON OD_DOC_CATS TO PROCEDURE FTV_M2M_PROCESS_EVENTS;
|
||||
GRANT SELECT ON OD_DOC_STATES TO PROCEDURE FTV_M2M_PROCESS_EVENTS;
|
||||
GRANT SELECT ON OD_VALUES TO PROCEDURE FTV_M2M_PROCESS_EVENTS;
|
||||
GRANT SELECT ON OD_VALUE_CODERS TO PROCEDURE FTV_M2M_PROCESS_EVENTS;
|
||||
GRANT SELECT ON OD_SYS_TABS TO PROCEDURE FTV_M2M_PROCESS_EVENTS;
|
||||
GRANT SELECT ON OD_STEP_TYPES TO PROCEDURE FTV_M2M_PROCESS_EVENTS;
|
||||
GRANT EXECUTE ON PROCEDURE FU_NEXT_DOC_NUM TO PROCEDURE FTV_M2M_PROCESS_EVENTS;
|
||||
GRANT EXECUTE ON PROCEDURE OD_PR_INS_WIRS TO PROCEDURE FTV_M2M_PROCESS_EVENTS;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_FAIL TO PROCEDURE FTV_M2M_PROCESS_EVENTS;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_GET TO PROCEDURE FTV_M2M_PROCESS_EVENTS;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_DT TO PROCEDURE FTV_M2M_PROCESS_EVENTS;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_DAY TO PROCEDURE FTV_M2M_PROCESS_EVENTS;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_PROCESS_WIRING TO PROCEDURE FTV_M2M_PROCESS_EVENTS;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_PROCESS_OPERATION TO PROCEDURE FTV_M2M_PROCESS_EVENTS;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_UPDATE_HANDBOOK TO PROCEDURE FTV_M2M_PROCESS_EVENTS;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_PROCESS_CREATE TO PROCEDURE FTV_M2M_PROCESS_EVENTS;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_JOB TO PROCEDURE FTV_M2M_PROCESS_EVENTS;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M TO PROCEDURE FTV_M2M_PROCESS_CREATE;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M_LINES TO PROCEDURE FTV_M2M_PROCESS_CREATE;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M_EVENTS TO PROCEDURE FTV_M2M_PROCESS_CREATE;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M_HANDBOOK TO PROCEDURE FTV_M2M_PROCESS_CREATE;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M_SETTINGS TO PROCEDURE FTV_M2M_PROCESS_CREATE;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_DOCS TO PROCEDURE FTV_M2M_PROCESS_CREATE;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_FACES TO PROCEDURE FTV_M2M_PROCESS_CREATE;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_FACE_CODERS TO PROCEDURE FTV_M2M_PROCESS_CREATE;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_U_FACES TO PROCEDURE FTV_M2M_PROCESS_CREATE;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_DOLS TO PROCEDURE FTV_M2M_PROCESS_CREATE;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_STEPS TO PROCEDURE FTV_M2M_PROCESS_CREATE;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_IN_DOCS TO PROCEDURE FTV_M2M_PROCESS_CREATE;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_OUT_DOCS TO PROCEDURE FTV_M2M_PROCESS_CREATE;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_INVALIDS TO PROCEDURE FTV_M2M_PROCESS_CREATE;
|
||||
GRANT SELECT, INSERT, UPDATE ON D_CLISER_M2M TO PROCEDURE FTV_M2M_PROCESS_CREATE;
|
||||
GRANT SELECT, INSERT, UPDATE ON D_CLISER_M2M_DOL TO PROCEDURE FTV_M2M_PROCESS_CREATE;
|
||||
GRANT SELECT, INSERT, UPDATE ON D_FILER TO PROCEDURE FTV_M2M_PROCESS_CREATE;
|
||||
GRANT SELECT, INSERT, UPDATE ON U_OP_INV_ZACH TO PROCEDURE FTV_M2M_PROCESS_CREATE;
|
||||
GRANT SELECT, INSERT, UPDATE ON U_OP_INV_SPIS TO PROCEDURE FTV_M2M_PROCESS_CREATE;
|
||||
GRANT SELECT ON OD_CLIENTS TO PROCEDURE FTV_M2M_PROCESS_CREATE;
|
||||
GRANT SELECT ON OD_CLIENTS_REC TO PROCEDURE FTV_M2M_PROCESS_CREATE;
|
||||
GRANT SELECT ON OD_DOC_CATS TO PROCEDURE FTV_M2M_PROCESS_CREATE;
|
||||
GRANT SELECT ON OD_DOC_STATES TO PROCEDURE FTV_M2M_PROCESS_CREATE;
|
||||
GRANT SELECT ON OD_VALUES TO PROCEDURE FTV_M2M_PROCESS_CREATE;
|
||||
GRANT SELECT ON OD_VALUE_CODERS TO PROCEDURE FTV_M2M_PROCESS_CREATE;
|
||||
GRANT SELECT ON OD_SYS_TABS TO PROCEDURE FTV_M2M_PROCESS_CREATE;
|
||||
GRANT SELECT ON OD_STEP_TYPES TO PROCEDURE FTV_M2M_PROCESS_CREATE;
|
||||
GRANT EXECUTE ON PROCEDURE FU_NEXT_DOC_NUM TO PROCEDURE FTV_M2M_PROCESS_CREATE;
|
||||
GRANT EXECUTE ON PROCEDURE OD_PR_INS_WIRS TO PROCEDURE FTV_M2M_PROCESS_CREATE;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_FAIL TO PROCEDURE FTV_M2M_PROCESS_CREATE;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_GET TO PROCEDURE FTV_M2M_PROCESS_CREATE;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_DT TO PROCEDURE FTV_M2M_PROCESS_CREATE;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_DAY TO PROCEDURE FTV_M2M_PROCESS_CREATE;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_PROCESS_WIRING TO PROCEDURE FTV_M2M_PROCESS_CREATE;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_PROCESS_OPERATION TO PROCEDURE FTV_M2M_PROCESS_CREATE;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_UPDATE_HANDBOOK TO PROCEDURE FTV_M2M_PROCESS_CREATE;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_PROCESS_EVENTS TO PROCEDURE FTV_M2M_PROCESS_CREATE;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_JOB TO PROCEDURE FTV_M2M_PROCESS_CREATE;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M TO PROCEDURE FTV_M2M_JOB;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M_LINES TO PROCEDURE FTV_M2M_JOB;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M_EVENTS TO PROCEDURE FTV_M2M_JOB;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M_HANDBOOK TO PROCEDURE FTV_M2M_JOB;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M_SETTINGS TO PROCEDURE FTV_M2M_JOB;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_DOCS TO PROCEDURE FTV_M2M_JOB;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_FACES TO PROCEDURE FTV_M2M_JOB;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_FACE_CODERS TO PROCEDURE FTV_M2M_JOB;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_U_FACES TO PROCEDURE FTV_M2M_JOB;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_DOLS TO PROCEDURE FTV_M2M_JOB;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_STEPS TO PROCEDURE FTV_M2M_JOB;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_IN_DOCS TO PROCEDURE FTV_M2M_JOB;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_OUT_DOCS TO PROCEDURE FTV_M2M_JOB;
|
||||
GRANT SELECT, INSERT, UPDATE ON OD_INVALIDS TO PROCEDURE FTV_M2M_JOB;
|
||||
GRANT SELECT, INSERT, UPDATE ON D_CLISER_M2M TO PROCEDURE FTV_M2M_JOB;
|
||||
GRANT SELECT, INSERT, UPDATE ON D_CLISER_M2M_DOL TO PROCEDURE FTV_M2M_JOB;
|
||||
GRANT SELECT, INSERT, UPDATE ON D_FILER TO PROCEDURE FTV_M2M_JOB;
|
||||
GRANT SELECT, INSERT, UPDATE ON U_OP_INV_ZACH TO PROCEDURE FTV_M2M_JOB;
|
||||
GRANT SELECT, INSERT, UPDATE ON U_OP_INV_SPIS TO PROCEDURE FTV_M2M_JOB;
|
||||
GRANT SELECT ON OD_CLIENTS TO PROCEDURE FTV_M2M_JOB;
|
||||
GRANT SELECT ON OD_CLIENTS_REC TO PROCEDURE FTV_M2M_JOB;
|
||||
GRANT SELECT ON OD_DOC_CATS TO PROCEDURE FTV_M2M_JOB;
|
||||
GRANT SELECT ON OD_DOC_STATES TO PROCEDURE FTV_M2M_JOB;
|
||||
GRANT SELECT ON OD_VALUES TO PROCEDURE FTV_M2M_JOB;
|
||||
GRANT SELECT ON OD_VALUE_CODERS TO PROCEDURE FTV_M2M_JOB;
|
||||
GRANT SELECT ON OD_SYS_TABS TO PROCEDURE FTV_M2M_JOB;
|
||||
GRANT SELECT ON OD_STEP_TYPES TO PROCEDURE FTV_M2M_JOB;
|
||||
GRANT EXECUTE ON PROCEDURE FU_NEXT_DOC_NUM TO PROCEDURE FTV_M2M_JOB;
|
||||
GRANT EXECUTE ON PROCEDURE OD_PR_INS_WIRS TO PROCEDURE FTV_M2M_JOB;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_FAIL TO PROCEDURE FTV_M2M_JOB;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_GET TO PROCEDURE FTV_M2M_JOB;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_DT TO PROCEDURE FTV_M2M_JOB;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_DAY TO PROCEDURE FTV_M2M_JOB;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_PROCESS_WIRING TO PROCEDURE FTV_M2M_JOB;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_PROCESS_OPERATION TO PROCEDURE FTV_M2M_JOB;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_UPDATE_HANDBOOK TO PROCEDURE FTV_M2M_JOB;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_PROCESS_EVENTS TO PROCEDURE FTV_M2M_JOB;
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_PROCESS_CREATE TO PROCEDURE FTV_M2M_JOB;
|
||||
|
||||
/* логин bj: запуск обработки и запись в таблицы обмена */
|
||||
GRANT EXECUTE ON PROCEDURE FTV_M2M_JOB TO :BJ_USER;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M TO :BJ_USER;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M_LINES TO :BJ_USER;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M_EVENTS TO :BJ_USER;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M_HANDBOOK TO :BJ_USER;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON FTV_M2M_SETTINGS TO :BJ_USER;
|
||||
COMMIT;
|
||||
@@ -250,11 +250,17 @@ func depoTablesFileName(f FancyMSSQLSettings) string {
|
||||
if f.IsPostgres() {
|
||||
return "bj-depo-tables-postgres.sql"
|
||||
}
|
||||
if f.IsFirebird() {
|
||||
return "bj-depo-tables-firebird.sql"
|
||||
}
|
||||
return "bj-depo-tables-mssql.sql"
|
||||
}
|
||||
|
||||
// depoTablesScript — скрипт для администратора СУБД (вариант «по скрипту»).
|
||||
func depoTablesScript(f FancyMSSQLSettings) string {
|
||||
if f.IsFirebird() { // таблицы у Firebird — вместе с привязками, процедурами и правами (depo_setup_fb.go)
|
||||
return depoSetupScriptFB(f, f.DepoDB(), depoSetupLogin(f), "")
|
||||
}
|
||||
pg := f.IsPostgres()
|
||||
db, user := f.DepoDB(), strings.TrimSpace(f.User)
|
||||
sep := "\nGO\n\n"
|
||||
@@ -285,7 +291,7 @@ func depoTablesScript(f FancyMSSQLSettings) string {
|
||||
}
|
||||
|
||||
func depoTableVisible(ctx context.Context, db *sql.DB, pg bool, name string) bool {
|
||||
q, arg := "SELECT CASE WHEN OBJECT_ID(@p1, 'U') IS NULL THEN 0 ELSE 1 END", "dbo."+name
|
||||
q, arg := fsql.Cat01, "dbo."+name
|
||||
if pg {
|
||||
q, arg = "SELECT to_regclass($1) IS NOT NULL", name
|
||||
}
|
||||
@@ -294,7 +300,7 @@ func depoTableVisible(ctx context.Context, db *sql.DB, pg bool, name string) boo
|
||||
}
|
||||
|
||||
func depoMissingColumns(ctx context.Context, db *sql.DB, pg bool, t depoTableSpec) []string {
|
||||
q, arg := "SELECT name FROM sys.columns WHERE object_id = OBJECT_ID(@p1)", "dbo."+t.Name
|
||||
q, arg := fsql.Cat02, "dbo."+t.Name
|
||||
if pg {
|
||||
q, arg = "SELECT column_name FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = lower($1)", t.Name
|
||||
}
|
||||
@@ -342,6 +348,9 @@ func depoTablesRightsHint(f FancyMSSQLSettings, login string, err error) string
|
||||
// (используется один раз, не сохраняется); пусто — логин bj из настроек. Если логин
|
||||
// другой, bj выдаёт своему логину права на таблицы.
|
||||
func depoTablesCreate(ctx context.Context, f FancyMSSQLSettings, adminUser, adminPass string) (string, error) {
|
||||
if f.IsFirebird() { // у Firebird таблицы ставятся вместе с привязками, процедурами и правами
|
||||
return depoProcsInstallFB(ctx, f, adminUser, adminPass)
|
||||
}
|
||||
pg := f.IsPostgres()
|
||||
conn, grantTo := f, ""
|
||||
if u := strings.TrimSpace(adminUser); u != "" {
|
||||
@@ -404,7 +413,7 @@ func depoTablesCheck(ctx context.Context, f FancyMSSQLSettings) (string, bool) {
|
||||
}
|
||||
var lacks []string
|
||||
for _, p := range []string{"SELECT", "INSERT", "UPDATE", "DELETE"} {
|
||||
q, arg := "SELECT ISNULL(HAS_PERMS_BY_NAME(@p1, 'OBJECT', @p2), 0)", "dbo."+t.Name
|
||||
q, arg := fsql.Cat03, "dbo."+t.Name
|
||||
if pg {
|
||||
q, arg = "SELECT has_table_privilege($1, $2)", t.Name
|
||||
}
|
||||
@@ -429,9 +438,9 @@ func depoTablesCheck(ctx context.Context, f FancyMSSQLSettings) (string, bool) {
|
||||
// DepoTablesView — данные карточки «Трансферные таблицы DEPO».
|
||||
type DepoTablesView struct {
|
||||
DB, User, Script, ScriptFile string
|
||||
Postgres bool
|
||||
Postgres, Firebird bool
|
||||
}
|
||||
|
||||
func depoTablesView(f FancyMSSQLSettings) DepoTablesView {
|
||||
return DepoTablesView{DB: f.DepoDB(), User: f.User, Script: depoTablesScript(f), ScriptFile: depoTablesFileName(f), Postgres: f.IsPostgres()}
|
||||
return DepoTablesView{DB: f.DepoDB(), User: f.User, Script: depoTablesScript(f), ScriptFile: depoTablesFileName(f), Postgres: f.IsPostgres(), Firebird: f.IsFirebird()}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,10 @@ import (
|
||||
|
||||
_ "github.com/jackc/pgx/v5/stdlib" // драйвер "pgx" для PostgreSQL
|
||||
_ "github.com/microsoft/go-mssqldb" // драйвер "sqlserver" для MSSQL
|
||||
_ "github.com/nakagami/firebirdsql" // драйвер "firebirdsql" для Firebird
|
||||
)
|
||||
|
||||
// Подключение к серверу учётной базы клиента Fancy DEPO — MSSQL или PostgreSQL,
|
||||
// Подключение к серверу учётной базы клиента Fancy DEPO — MSSQL, PostgreSQL или Firebird,
|
||||
// SQL-логин. База выбирается в настройках (DepoDB).
|
||||
|
||||
// validDBName — безопасное имя БД/идентификатора MSSQL (буквы/цифры/_), чтобы
|
||||
@@ -35,6 +36,9 @@ func fancyDriver(s FancyMSSQLSettings) string {
|
||||
if s.IsPostgres() {
|
||||
return "pgx"
|
||||
}
|
||||
if s.IsFirebird() {
|
||||
return "firebirdsql"
|
||||
}
|
||||
return "sqlserver"
|
||||
}
|
||||
|
||||
@@ -49,6 +53,9 @@ func fancyAdminDB(s FancyMSSQLSettings) string {
|
||||
|
||||
// fancyDSN строит строку подключения. db="" → к служебной БД (для CREATE DATABASE).
|
||||
func fancyDSN(s FancyMSSQLSettings, db string) string {
|
||||
if s.IsFirebird() {
|
||||
return fbDSN(s, db)
|
||||
}
|
||||
port := s.DefaultPort()
|
||||
if s.IsPostgres() {
|
||||
ssl := s.Encrypt
|
||||
@@ -95,6 +102,9 @@ func fancyOpen(s FancyMSSQLSettings, db string) (*sql.DB, error) {
|
||||
if s.IsPostgres() && db == "" {
|
||||
db = fancyAdminDB(s)
|
||||
}
|
||||
if s.IsFirebird() && db == "" { // служебной базы у Firebird нет: подключение к серверу — это подключение к базе
|
||||
db = s.DepoDB()
|
||||
}
|
||||
conn, err := fancyOpenGated(s, fancyDSN(s, db)) // после неудачного входа — пауза, чтобы не заблокировать логин
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -121,6 +131,9 @@ func fancyPing(ctx context.Context, s FancyMSSQLSettings, db string) (string, er
|
||||
if s.IsPostgres() {
|
||||
verQuery = "SELECT version()"
|
||||
}
|
||||
if s.IsFirebird() {
|
||||
verQuery = "SELECT 'Firebird ' || RDB$GET_CONTEXT('SYSTEM', 'ENGINE_VERSION') FROM RDB$DATABASE"
|
||||
}
|
||||
_ = conn.QueryRowContext(cctx, verQuery).Scan(&ver)
|
||||
ver = strings.TrimSpace(ver)
|
||||
if i := strings.IndexAny(ver, "\r\n"); i > 0 {
|
||||
|
||||
@@ -39,16 +39,12 @@ func (h *setupHandlers) registerFancy(mux *http.ServeMux) {
|
||||
func (h *setupHandlers) fancyFromForm(r *http.Request) FancyMSSQLSettings {
|
||||
cur := h.rc.Snapshot().Fancy
|
||||
dbType := strings.TrimSpace(r.FormValue("db_type"))
|
||||
if dbType != "postgres" {
|
||||
if dbType != "postgres" && dbType != "firebird" {
|
||||
dbType = "mssql"
|
||||
}
|
||||
port, _ := strconv.Atoi(strings.TrimSpace(r.FormValue("port")))
|
||||
if port == 0 {
|
||||
if dbType == "postgres" {
|
||||
port = 5432
|
||||
} else {
|
||||
port = 1433
|
||||
}
|
||||
port = map[string]int{"mssql": 1433, "postgres": 5432, "firebird": 3050}[dbType]
|
||||
}
|
||||
pass := r.FormValue("password")
|
||||
if pass == "" {
|
||||
@@ -178,6 +174,12 @@ func (h *setupHandlers) checkFancyDepoTables(w http.ResponseWriter, r *http.Requ
|
||||
func (h *setupHandlers) fancyDepoTablesScript(w http.ResponseWriter, r *http.Request) {
|
||||
s := h.rc.Snapshot().Fancy
|
||||
body := depoTablesScript(s)
|
||||
if s.IsFirebird() { // isql -ch WIN1251
|
||||
w.Header().Set("Content-Type", "application/sql; charset=windows-1251")
|
||||
w.Header().Set("Content-Disposition", `attachment; filename="`+depoTablesFileName(s)+`"`)
|
||||
_, _ = w.Write(win1251(body))
|
||||
return
|
||||
}
|
||||
if !s.IsPostgres() {
|
||||
body = "\uFEFF" + body // SSMS без BOM читает кириллицу в комментариях как ANSI
|
||||
}
|
||||
@@ -291,10 +293,7 @@ func (h *setupHandlers) saveFancy(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
s := h.fancyFromForm(r)
|
||||
dbLabel := "MSSQL"
|
||||
if s.IsPostgres() {
|
||||
dbLabel = "PostgreSQL"
|
||||
}
|
||||
dbLabel := fancyDBLabel(s)
|
||||
if s.Server == "" || s.User == "" {
|
||||
setupFlash(w, r, dbLabel+": укажите сервер и пользователя")
|
||||
return
|
||||
@@ -317,8 +316,13 @@ func (h *setupHandlers) testFancy(w http.ResponseWriter, r *http.Request) {
|
||||
setupFlash(w, r, "MSSQL: сначала сохраните параметры подключения")
|
||||
return
|
||||
}
|
||||
// Проверяем соединение к серверу (master) — БД может ещё не существовать.
|
||||
ver, err := fancyPing(depoLoginForce(r.Context()), s, "master")
|
||||
// Проверяем соединение к серверу (master) — БД может ещё не существовать. У Firebird служебной базы нет —
|
||||
// подключение к базе DEPO.
|
||||
db := "master"
|
||||
if s.IsFirebird() {
|
||||
db = s.DepoDB()
|
||||
}
|
||||
ver, err := fancyPing(depoLoginForce(r.Context()), s, db)
|
||||
if err != nil {
|
||||
setupFlash(w, r, fancyDBLabel(s)+": соединение не удалось — "+err.Error())
|
||||
return
|
||||
@@ -331,6 +335,9 @@ func fancyDBLabel(s FancyMSSQLSettings) string {
|
||||
if s.IsPostgres() {
|
||||
return "PostgreSQL"
|
||||
}
|
||||
if s.IsFirebird() {
|
||||
return "Firebird"
|
||||
}
|
||||
return "MSSQL"
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,470 @@
|
||||
package lkgateway
|
||||
|
||||
// Fancy DEPO на Firebird (27.10.2026). Fancy работает и на Firebird 2.5 — те же таблицы и процедуры ядра, что в
|
||||
// MSSQL-версии (MSSQL-версия Fancy — перевод Firebird-версии). Весь SQL bj к DEPO написан на T-SQL; чтобы не
|
||||
// держать второй текст в каждом месте вызова, подключение к Firebird переводит запросы само (fbConn):
|
||||
// - точный текст Firebird — для запросов, которые механически не переводятся (fsql.FB, ключ — текст T-SQL);
|
||||
// - остальное механически, вне строк и комментариев: без dbo., TOP n — FIRST n, ISNULL — COALESCE,
|
||||
// GETDATE() — CURRENT_TIMESTAMP, LEN — CHAR_LENGTH, N'…' — '…', «exec имя» — EXECUTE PROCEDURE имя;
|
||||
// - именованные параметры @имя (sql.Named) — позиционные ? в порядке появления в тексте;
|
||||
// - GUID в параметрах — в верхнем регистре (в таблицах обмена Firebird GUID — строка в верхнем регистре,
|
||||
// uniqueidentifier MSSQL сравнивается без учёта регистра); время «2006-01-02T15:04:05» — с пробелом вместо T.
|
||||
// База Fancy на Firebird обычно в диалекте 1 и кодировке NONE с данными в WIN1251 — подключение в WIN1251.
|
||||
// Таблицы, процедуры и права для Firebird ставит depo_setup_fb.go; DDL сюда не попадает.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/lkgateway/fsql"
|
||||
"github.com/nakagami/firebirdsql"
|
||||
)
|
||||
|
||||
// fbDSN — строка подключения драйвера firebirdsql: путь к файлу базы на сервере Firebird или псевдоним.
|
||||
func fbDSN(s FancyMSSQLSettings, db string) string {
|
||||
q := url.Values{}
|
||||
q.Set("charset", "WIN1251")
|
||||
u := &url.URL{
|
||||
Scheme: "firebird",
|
||||
User: url.UserPassword(s.User, s.Password),
|
||||
Host: fmt.Sprintf("%s:%d", s.Server, s.DefaultPort()),
|
||||
Path: "/" + db,
|
||||
RawQuery: q.Encode(),
|
||||
}
|
||||
return u.String()
|
||||
}
|
||||
|
||||
// validFBPath — путь к файлу базы Firebird на сервере (D:\Fancy\DEPO.GDB, /var/db/depo.fdb) или псевдоним
|
||||
// из aliases.conf: без кавычек, точки с запятой и управляющих символов (путь уходит в строку подключения).
|
||||
func validFBPath(s string) bool {
|
||||
if strings.TrimSpace(s) == "" || len(s) > 255 {
|
||||
return false
|
||||
}
|
||||
for _, c := range s {
|
||||
if c < ' ' || strings.ContainsRune(`'";?#`, c) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// fbIsLoginFailure — Firebird отверг логин или пароль.
|
||||
func fbIsLoginFailure(err error) bool {
|
||||
var fe *firebirdsql.FbError
|
||||
if !errors.As(err, &fe) {
|
||||
return false
|
||||
}
|
||||
for _, c := range fe.GDSCodes {
|
||||
if c == firebirdsql.ISCLogin || c == firebirdsql.ISCLoginError {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// fbConnector — подключения к Firebird с переводом запросов.
|
||||
type fbConnector struct{ base driver.Connector }
|
||||
|
||||
func (c fbConnector) Driver() driver.Driver { return c.base.Driver() }
|
||||
|
||||
func (c fbConnector) Connect(ctx context.Context) (driver.Conn, error) {
|
||||
conn, err := c.base.Connect(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &fbConn{c: conn}, nil
|
||||
}
|
||||
|
||||
// fbConn — подключение драйвера firebirdsql, которому database/sql отдаёт T-SQL bj: текст и параметры
|
||||
// переводятся в fbRewrite. Остальные методы — как у драйвера.
|
||||
type fbConn struct{ c driver.Conn }
|
||||
|
||||
func (f *fbConn) Prepare(query string) (driver.Stmt, error) {
|
||||
return f.PrepareContext(context.Background(), query)
|
||||
}
|
||||
|
||||
func (f *fbConn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) {
|
||||
q, names := query, []string(nil)
|
||||
if !fbNative(query) {
|
||||
q, names = fbParams(fbSQL(query))
|
||||
}
|
||||
var st driver.Stmt
|
||||
var err error
|
||||
if pc, ok := f.c.(driver.ConnPrepareContext); ok {
|
||||
st, err = pc.PrepareContext(ctx, q)
|
||||
} else {
|
||||
st, err = f.c.Prepare(q)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fbErr(query, q, err)
|
||||
}
|
||||
return &fbStmt{st: st, names: names}, nil
|
||||
}
|
||||
|
||||
func (f *fbConn) Close() error { return f.c.Close() }
|
||||
|
||||
func (f *fbConn) Begin() (driver.Tx, error) {
|
||||
return f.BeginTx(context.Background(), driver.TxOptions{})
|
||||
}
|
||||
|
||||
func (f *fbConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
|
||||
if bt, ok := f.c.(driver.ConnBeginTx); ok {
|
||||
return bt.BeginTx(ctx, opts)
|
||||
}
|
||||
return f.c.Begin() //nolint:staticcheck // у драйвера без ConnBeginTx другого способа нет
|
||||
}
|
||||
|
||||
func (f *fbConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
|
||||
ex, ok := f.c.(driver.ExecerContext)
|
||||
if !ok {
|
||||
return nil, driver.ErrSkip
|
||||
}
|
||||
q, a, err := fbRewrite(query, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := ex.ExecContext(ctx, q, a)
|
||||
return res, fbErr(query, q, err)
|
||||
}
|
||||
|
||||
func (f *fbConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
|
||||
qr, ok := f.c.(driver.QueryerContext)
|
||||
if !ok {
|
||||
return nil, driver.ErrSkip
|
||||
}
|
||||
q, a, err := fbRewrite(query, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rows, err := qr.QueryContext(ctx, q, a)
|
||||
return rows, fbErr(query, q, err)
|
||||
}
|
||||
|
||||
func (f *fbConn) Ping(ctx context.Context) error {
|
||||
if p, ok := f.c.(driver.Pinger); ok {
|
||||
return p.Ping(ctx)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fbConn) CheckNamedValue(nv *driver.NamedValue) error {
|
||||
if c, ok := f.c.(driver.NamedValueChecker); ok {
|
||||
return c.CheckNamedValue(nv)
|
||||
}
|
||||
return driver.ErrSkip
|
||||
}
|
||||
|
||||
func (f *fbConn) IsValid() bool {
|
||||
if v, ok := f.c.(driver.Validator); ok {
|
||||
return v.IsValid()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (f *fbConn) ResetSession(ctx context.Context) error {
|
||||
if r, ok := f.c.(driver.SessionResetter); ok {
|
||||
return r.ResetSession(ctx)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// fbStmt — подготовленный запрос: параметры при выполнении расставляются по именам из текста.
|
||||
type fbStmt struct {
|
||||
st driver.Stmt
|
||||
names []string
|
||||
}
|
||||
|
||||
func (s *fbStmt) Close() error { return s.st.Close() }
|
||||
func (s *fbStmt) NumInput() int { return -1 }
|
||||
|
||||
func (s *fbStmt) Exec(args []driver.Value) (driver.Result, error) {
|
||||
return s.ExecContext(context.Background(), fbValues(args))
|
||||
}
|
||||
|
||||
func (s *fbStmt) Query(args []driver.Value) (driver.Rows, error) {
|
||||
return s.QueryContext(context.Background(), fbValues(args))
|
||||
}
|
||||
|
||||
func (s *fbStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {
|
||||
a, err := fbArgs(s.names, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ex, ok := s.st.(driver.StmtExecContext); ok {
|
||||
return ex.ExecContext(ctx, a)
|
||||
}
|
||||
return s.st.Exec(fbPlain(a)) //nolint:staticcheck // драйвер без StmtExecContext
|
||||
}
|
||||
|
||||
func (s *fbStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
|
||||
a, err := fbArgs(s.names, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if q, ok := s.st.(driver.StmtQueryContext); ok {
|
||||
return q.QueryContext(ctx, a)
|
||||
}
|
||||
return s.st.Query(fbPlain(a)) //nolint:staticcheck // драйвер без StmtQueryContext
|
||||
}
|
||||
|
||||
func fbValues(v []driver.Value) []driver.NamedValue {
|
||||
out := make([]driver.NamedValue, len(v))
|
||||
for i, x := range v {
|
||||
out[i] = driver.NamedValue{Ordinal: i + 1, Value: x}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func fbPlain(a []driver.NamedValue) []driver.Value {
|
||||
out := make([]driver.Value, len(a))
|
||||
for i, x := range a {
|
||||
out[i] = x.Value
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// fbRewrite — текст и параметры запроса T-SQL для Firebird. DDL и блоки PSQL (установка, depo_setup_fb.go)
|
||||
// написаны для Firebird — уходят как есть.
|
||||
func fbRewrite(query string, args []driver.NamedValue) (string, []driver.NamedValue, error) {
|
||||
if fbNative(query) {
|
||||
return query, args, nil
|
||||
}
|
||||
q, names := fbParams(fbSQL(query))
|
||||
a, err := fbArgs(names, args)
|
||||
return q, a, err
|
||||
}
|
||||
|
||||
var fbNativeRe = regexp.MustCompile(`(?i)^\s*(CREATE|ALTER|RECREATE|DROP|GRANT|REVOKE|EXECUTE\s+BLOCK|COMMENT\s+ON)\b`)
|
||||
|
||||
// fbNative — команда уже на Firebird (установка таблиц, процедур, прав).
|
||||
func fbNative(q string) bool { return fbNativeRe.MatchString(fbStripComments(q)) }
|
||||
|
||||
var (
|
||||
fbExecRe = regexp.MustCompile(`(?is)^\s*exec(?:ute)?\s+(?:\[?dbo\]?\.)?\[?([A-Za-z0-9_]+)\]?\s*;?\s*$`)
|
||||
fbObjectIDRe = regexp.MustCompile(`(?i)^SELECT OBJECT_ID\('(?:dbo\.)?([A-Za-z0-9_]+)'\)$`)
|
||||
fbDboRe = regexp.MustCompile(`(?i)\[?\bdbo\]?\.`)
|
||||
fbIsNullRe = regexp.MustCompile(`(?i)\bISNULL\s*\(`)
|
||||
fbGetDateRe = regexp.MustCompile(`(?i)\bGETDATE\s*\(\s*\)`)
|
||||
fbLenRe = regexp.MustCompile(`(?i)\bLEN\s*\(`)
|
||||
fbTopRe = regexp.MustCompile(`(?i)\bSELECT(\s+DISTINCT)?\s+TOP\s*(\(\s*@?\w+\s*\)|\d+)`)
|
||||
fbGUIDRe = regexp.MustCompile(`^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$`)
|
||||
fbISOTimeRe = regexp.MustCompile(`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d+)?)?$`)
|
||||
)
|
||||
|
||||
// fbSQL — текст запроса для Firebird: точный перевод из fsql.FB, иначе механический.
|
||||
func fbSQL(q string) string {
|
||||
if t, ok := fsql.FB[q]; ok {
|
||||
return t
|
||||
}
|
||||
if m := fbExecRe.FindStringSubmatch(q); m != nil {
|
||||
return "EXECUTE PROCEDURE " + m[1]
|
||||
}
|
||||
if m := fbObjectIDRe.FindStringSubmatch(q); m != nil { // depoTableExists: NULL — таблицы нет
|
||||
return "SELECT (SELECT FIRST 1 1 FROM RDB$RELATIONS WHERE RDB$RELATION_NAME = '" + strings.ToUpper(m[1]) + "') FROM RDB$DATABASE"
|
||||
}
|
||||
return fbOutsideLiterals(q, func(code string) string {
|
||||
code = fbDboRe.ReplaceAllString(code, "")
|
||||
code = fbIsNullRe.ReplaceAllString(code, "COALESCE(")
|
||||
code = fbGetDateRe.ReplaceAllString(code, "CURRENT_TIMESTAMP")
|
||||
code = fbLenRe.ReplaceAllString(code, "CHAR_LENGTH(")
|
||||
return fbTopRe.ReplaceAllStringFunc(code, func(s string) string {
|
||||
m := fbTopRe.FindStringSubmatch(s)
|
||||
return "SELECT FIRST " + m[2] + m[1] // в Firebird FIRST — до DISTINCT
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// fbOutsideLiterals — замены только в коде: строки, имена в кавычках и комментарии не трогаются; N перед
|
||||
// строкой (N'…' — национальная строка T-SQL) убирается.
|
||||
func fbOutsideLiterals(q string, fix func(string) string) string {
|
||||
var b, code strings.Builder
|
||||
emit := func(lit string) {
|
||||
c := code.String()
|
||||
code.Reset()
|
||||
if strings.HasPrefix(lit, "'") && (strings.HasSuffix(c, "N") || strings.HasSuffix(c, "n")) &&
|
||||
(len(c) == 1 || !fbIdentChar(c[len(c)-2])) {
|
||||
c = c[:len(c)-1]
|
||||
}
|
||||
b.WriteString(fix(c))
|
||||
b.WriteString(lit)
|
||||
}
|
||||
for i := 0; i < len(q); i++ {
|
||||
c := q[i]
|
||||
switch {
|
||||
case c == '\'' || c == '"':
|
||||
j := i + 1
|
||||
for j < len(q) {
|
||||
if q[j] == c {
|
||||
if j+1 < len(q) && q[j+1] == c {
|
||||
j += 2
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
j++
|
||||
}
|
||||
if j >= len(q) {
|
||||
j = len(q) - 1
|
||||
}
|
||||
emit(q[i : j+1])
|
||||
i = j
|
||||
case c == '-' && i+1 < len(q) && q[i+1] == '-':
|
||||
j := strings.IndexByte(q[i:], '\n')
|
||||
if j < 0 {
|
||||
j = len(q) - i
|
||||
}
|
||||
emit(q[i : i+j])
|
||||
i += j - 1
|
||||
case c == '/' && i+1 < len(q) && q[i+1] == '*':
|
||||
j := strings.Index(q[i+2:], "*/")
|
||||
end := len(q)
|
||||
if j >= 0 {
|
||||
end = i + 2 + j + 2
|
||||
}
|
||||
emit(q[i:end])
|
||||
i = end - 1
|
||||
default:
|
||||
code.WriteByte(c)
|
||||
}
|
||||
}
|
||||
b.WriteString(fix(code.String()))
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// fbParams — @имя вне строк и комментариев заменяются на ?; возвращает имена по порядку (повторы — повторно).
|
||||
func fbParams(q string) (string, []string) {
|
||||
if !strings.Contains(q, "@") {
|
||||
return q, nil
|
||||
}
|
||||
var b strings.Builder
|
||||
var names []string
|
||||
for i := 0; i < len(q); i++ {
|
||||
c := q[i]
|
||||
switch {
|
||||
case c == '\'' || c == '"': // строка или имя в кавычках — как есть до закрывающей
|
||||
j := i + 1
|
||||
for j < len(q) {
|
||||
if q[j] == c {
|
||||
if j+1 < len(q) && q[j+1] == c {
|
||||
j += 2
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
j++
|
||||
}
|
||||
if j >= len(q) {
|
||||
j = len(q) - 1
|
||||
}
|
||||
b.WriteString(q[i : j+1])
|
||||
i = j
|
||||
case c == '-' && i+1 < len(q) && q[i+1] == '-':
|
||||
j := strings.IndexByte(q[i:], '\n')
|
||||
if j < 0 {
|
||||
j = len(q) - i
|
||||
}
|
||||
b.WriteString(q[i : i+j])
|
||||
i += j - 1
|
||||
case c == '/' && i+1 < len(q) && q[i+1] == '*':
|
||||
j := strings.Index(q[i+2:], "*/")
|
||||
end := len(q)
|
||||
if j >= 0 {
|
||||
end = i + 2 + j + 2
|
||||
}
|
||||
b.WriteString(q[i:end])
|
||||
i = end - 1
|
||||
case c == '@' && i+1 < len(q) && fbIdentStart(q[i+1]):
|
||||
j := i + 1
|
||||
for j < len(q) && fbIdentChar(q[j]) {
|
||||
j++
|
||||
}
|
||||
names = append(names, q[i+1:j])
|
||||
b.WriteByte('?')
|
||||
i = j - 1
|
||||
default:
|
||||
b.WriteByte(c)
|
||||
}
|
||||
}
|
||||
return b.String(), names
|
||||
}
|
||||
|
||||
func fbIdentStart(c byte) bool { return c == '_' || c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' }
|
||||
func fbIdentChar(c byte) bool { return fbIdentStart(c) || c >= '0' && c <= '9' }
|
||||
|
||||
// fbArgs — значения для ? по именам из текста: sql.Named — по имени, @p1…@pN без имени — по номеру.
|
||||
// Без имён в тексте параметры передаются как есть (запрос уже с ?).
|
||||
func fbArgs(names []string, args []driver.NamedValue) ([]driver.NamedValue, error) {
|
||||
if len(names) == 0 {
|
||||
out := make([]driver.NamedValue, len(args))
|
||||
for i, a := range args {
|
||||
out[i] = driver.NamedValue{Ordinal: i + 1, Value: fbValue(a.Value)}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
out := make([]driver.NamedValue, 0, len(names))
|
||||
for _, n := range names {
|
||||
v, ok := fbArg(n, args)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("параметр @%s не передан", n)
|
||||
}
|
||||
out = append(out, driver.NamedValue{Ordinal: len(out) + 1, Value: fbValue(v)})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func fbArg(name string, args []driver.NamedValue) (driver.Value, bool) {
|
||||
for _, a := range args {
|
||||
if a.Name != "" && strings.EqualFold(a.Name, name) {
|
||||
return a.Value, true
|
||||
}
|
||||
}
|
||||
if len(name) > 1 && (name[0] == 'p' || name[0] == 'P') {
|
||||
if n, err := strconv.Atoi(name[1:]); err == nil && n >= 1 {
|
||||
for _, a := range args {
|
||||
if a.Name == "" && a.Ordinal == n {
|
||||
return a.Value, true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// fbValue — GUID в верхнем регистре, время ISO 8601 — с пробелом вместо T (Firebird «T» не разбирает).
|
||||
func fbValue(v driver.Value) driver.Value {
|
||||
s, ok := v.(string)
|
||||
if !ok {
|
||||
return v
|
||||
}
|
||||
switch {
|
||||
case fbGUIDRe.MatchString(s):
|
||||
return strings.ToUpper(s)
|
||||
case fbISOTimeRe.MatchString(s):
|
||||
return strings.Replace(s, "T", " ", 1)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
var fbErrLogged sync.Map
|
||||
|
||||
// fbErr — ошибка Firebird; для разбора в журнал (раз на запрос) — исходный текст T-SQL и что ушло в Firebird.
|
||||
func fbErr(orig, sent string, err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
var fe *firebirdsql.FbError
|
||||
if errors.As(err, &fe) && fe.SQLCode == -104 { // синтаксис: перевод запроса не подошёл
|
||||
if _, seen := fbErrLogged.LoadOrStore(orig, true); !seen {
|
||||
log.Printf("depo-fb: запрос не принят Firebird: %v\n T-SQL: %s\n Firebird: %s", err, depoStmtHead(orig), sent)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package lkgateway
|
||||
|
||||
// Живая проверка перевода запросов на Firebird: каждый запрос bj к DEPO (fsql) готовится (PREPARE, без
|
||||
// выполнения) на настоящей базе Fancy. Запуск — с подключением к копии базы:
|
||||
// BJ_FB_TEST_DSN='firebird://SYSDBA:пароль@127.0.0.1:3050/путь_к_базе?charset=WIN1251' go test -run FBLive ./internal/lkgateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/lkgateway/fsql"
|
||||
)
|
||||
|
||||
func readRepoFile(p string) ([]byte, error) { return os.ReadFile(filepath.Join("..", "..", p)) }
|
||||
|
||||
// fbLiveTodo — личный кабинет и ручной ввод: на Firebird — следующий этап (YEAR(), %, сложение строк).
|
||||
var fbLiveTodo = map[string]bool{"Arch02": true, "Arch03": true, "Arch07": true, "Arch08": true, "Arch10": true, "Hist02": true, "Man01": true}
|
||||
|
||||
// fbLiveSkip — не к DEPO (PostgreSQL bj и экспорт QUIK, база МНП QUIK, SPECTRE) или куски составных запросов.
|
||||
var fbLiveSkip = map[string]bool{
|
||||
"Doc02": true, "Doc03": true, "Doc04": true, "Sn01": true, "Sn02": true, "Sn03": true, "Sn04": true, "Sn05": true,
|
||||
"On01": true, "On02": true, "On03": true, "Pr01": true, "Qk01": true, "Cl01": true, "Tbl01": true, "Tbl02": true, "QTb01": true,
|
||||
"Ev05": true, // DEPONENT_ID — пишется, только если колонка есть (depoHasColumn)
|
||||
"Que04": true, "Que05": true,
|
||||
"Ev06": true, "Ev07": true, "Ev08": true, "Ev09": true, "Que01": true, "Que02": true, "Que03": true, "Flow01": true, "Flow02": true,
|
||||
}
|
||||
|
||||
// fbLiveQueries — строки-запросы пакета fsql по именам; шаблоны заполнены так, как их заполняет код.
|
||||
func fbLiveQueries(t *testing.T) map[string]string {
|
||||
fset := token.NewFileSet()
|
||||
pkgs, err := parser.ParseDir(fset, "fsql", nil, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
out := map[string]string{}
|
||||
for _, p := range pkgs {
|
||||
for _, f := range p.Files {
|
||||
for _, d := range f.Decls {
|
||||
g, ok := d.(*ast.GenDecl)
|
||||
if !ok || g.Tok != token.VAR {
|
||||
continue
|
||||
}
|
||||
for _, s := range g.Specs {
|
||||
vs := s.(*ast.ValueSpec)
|
||||
for i, n := range vs.Names {
|
||||
if i >= len(vs.Values) {
|
||||
continue
|
||||
}
|
||||
lit, ok := vs.Values[i].(*ast.BasicLit)
|
||||
if !ok || lit.Kind != token.STRING || fbLiveSkip[n.Name] {
|
||||
continue
|
||||
}
|
||||
q, err := strconv.Unquote(lit.Value)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
q = strings.ReplaceAll(q, "{ACC}", "ВТНКЦ")
|
||||
q = strings.ReplaceAll(q, "%d", "10")
|
||||
q = strings.ReplaceAll(q, "%s", "1")
|
||||
out[n.Name] = q
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
doc := "CAST(m.DOC AS varchar(32))"
|
||||
out["Ev07+"] = fsql.Ev07 + doc + fsql.Ev08 + doc + fsql.Ev09 + doc + `)
|
||||
FROM dbo.FTV_M2M m WHERE m.TimeNew > DATEADD(day,-45,GETDATE()) ORDER BY m.ID DESC`
|
||||
out["Que01+"] = fsql.Que01 + "m.TimeNew" + `, ISNULL(` + doc + fsql.Que02 + doc + fsql.Que03 + doc + `)
|
||||
FROM dbo.FTV_M2M m ORDER BY m.TimeNew DESC`
|
||||
out["Flow01+"] = fsql.Flow01 + doc + fsql.Flow02
|
||||
out["Que04+"] = fsql.Que04 + "TimeNew DESC)"
|
||||
out["Que05+"] = fsql.Que05 + "TimeNew DESC) ORDER BY e.EVENT_DATE"
|
||||
out["Ev06+"] = fsql.Ev06 + "GUID, REFERENCE, ISIN, SEC_CODE, AMOUNT, PLACE_NUMBER, LINK_NUMBER, SHARE, DEPO_NUMBER, TimeNew) VALUES (@g, @r, @i, @c, @a, @p, @lnk, @sh, @acc, GETDATE())"
|
||||
return out
|
||||
}
|
||||
|
||||
func TestFBLivePrepare(t *testing.T) {
|
||||
dsn := os.Getenv("BJ_FB_TEST_DSN")
|
||||
if dsn == "" {
|
||||
t.Skip("BJ_FB_TEST_DSN не задан")
|
||||
}
|
||||
probe, err := sql.Open("firebirdsql", dsn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
base, err := probe.Driver().(driver.DriverContext).OpenConnector(dsn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
db := sql.OpenDB(fbConnector{base: base})
|
||||
defer db.Close()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
||||
defer cancel()
|
||||
qs := fbLiveQueries(t)
|
||||
names := make([]string, 0, len(qs))
|
||||
for n := range qs {
|
||||
names = append(names, n)
|
||||
}
|
||||
sort.Strings(names)
|
||||
var bad []string
|
||||
todo := 0
|
||||
for _, n := range names {
|
||||
st, err := db.PrepareContext(ctx, qs[n])
|
||||
if err != nil {
|
||||
if fbLiveTodo[n] {
|
||||
todo++
|
||||
t.Logf("(следующий этап) %s: %v", n, strings.ReplaceAll(err.Error(), "\n", " "))
|
||||
continue
|
||||
}
|
||||
bad = append(bad, fmt.Sprintf("%s: %v\n %s", n, err, depoStmtHead(fbSQL(qs[n]))))
|
||||
continue
|
||||
}
|
||||
st.Close()
|
||||
}
|
||||
t.Logf("подготовлено %d из %d запросов, отложено %d", len(names)-len(bad)-todo, len(names), todo)
|
||||
for _, b := range bad {
|
||||
t.Log(b)
|
||||
}
|
||||
if len(bad) > 0 {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
var _ driver.Connector = fbConnector{}
|
||||
@@ -0,0 +1,117 @@
|
||||
package lkgateway
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/lkgateway/fsql"
|
||||
)
|
||||
|
||||
func TestFBSQLMechanical(t *testing.T) {
|
||||
cases := []struct{ in, want string }{
|
||||
{"SELECT TOP 1 ID FROM OD_VALUES WHERE ISIN=@i ORDER BY ID", "SELECT FIRST 1 ID FROM OD_VALUES WHERE ISIN=@i ORDER BY ID"},
|
||||
{"SELECT DISTINCT TOP (5000) PORT_NUM FROM OD_CLIENTS_PORTS", "SELECT FIRST (5000) DISTINCT PORT_NUM FROM OD_CLIENTS_PORTS"},
|
||||
{"SELECT ISNULL(STATE,'') FROM dbo.FTV_M2M_EVENTS WHERE GUID=@g", "SELECT COALESCE(STATE,'') FROM FTV_M2M_EVENTS WHERE GUID=@g"},
|
||||
{"INSERT INTO [dbo].FTV_M2M_EVENTS (EVENT_TYPE, EVENT_DATE) VALUES ('HANDBOOK', GETDATE())", "INSERT INTO FTV_M2M_EVENTS (EVENT_TYPE, EVENT_DATE) VALUES ('HANDBOOK', CURRENT_TIMESTAMP)"},
|
||||
// N'…' — без N; внутри строк и комментариев ничего не меняется
|
||||
{"SELECT 1 FROM OD_FACES WHERE MNEM = N'НДЦ' AND NAME <> ' N' /* ISNULL( dbo.x */", "SELECT 1 FROM OD_FACES WHERE MNEM = 'НДЦ' AND NAME <> ' N' /* ISNULL( dbo.x */"},
|
||||
{"SELECT 'TOP 1 ISNULL(' FROM RDB$DATABASE", "SELECT 'TOP 1 ISNULL(' FROM RDB$DATABASE"},
|
||||
{"exec dbo.FTV_M2M_JOB", "EXECUTE PROCEDURE FTV_M2M_JOB"},
|
||||
{"SELECT OBJECT_ID('dbo.FTV_M2M')", "SELECT (SELECT FIRST 1 1 FROM RDB$RELATIONS WHERE RDB$RELATION_NAME = 'FTV_M2M') FROM RDB$DATABASE"},
|
||||
{fsql.Hlth03, fsql.FB[fsql.Hlth03]},
|
||||
}
|
||||
for _, c := range cases {
|
||||
if got := fbSQL(c.in); got != c.want {
|
||||
t.Errorf("fbSQL(%q)\n got %q\nwant %q", c.in, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFBParamsAndArgs(t *testing.T) {
|
||||
q, names := fbParams("SELECT @g, '@x', @r /* @y */, @g -- @z\nFROM T WHERE A=@p1")
|
||||
if q != "SELECT ?, '@x', ? /* @y */, ? -- @z\nFROM T WHERE A=?" {
|
||||
t.Fatalf("q = %q", q)
|
||||
}
|
||||
if !reflect.DeepEqual(names, []string{"g", "r", "g", "p1"}) {
|
||||
t.Fatalf("names = %v", names)
|
||||
}
|
||||
args := []driver.NamedValue{
|
||||
{Name: "r", Ordinal: 1, Value: "ref"},
|
||||
{Name: "g", Ordinal: 2, Value: "bae1fa60-eb0a-4fee-b12c-018d1eafcbd2"},
|
||||
{Ordinal: 3, Value: int64(7)},
|
||||
{Name: "unused", Ordinal: 4, Value: "x"},
|
||||
}
|
||||
// @p1 — первый параметр без имени (Ordinal 1 в порядке вызова): здесь его нет — ошибка
|
||||
if _, err := fbArgs(names, args); err == nil {
|
||||
t.Fatal("ожидалась ошибка: @p1 без позиционного параметра")
|
||||
}
|
||||
got, err := fbArgs([]string{"g", "r", "g"}, args)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := []any{"BAE1FA60-EB0A-4FEE-B12C-018D1EAFCBD2", "ref", "BAE1FA60-EB0A-4FEE-B12C-018D1EAFCBD2"}
|
||||
for i, a := range got {
|
||||
if a.Value != want[i] || a.Ordinal != i+1 {
|
||||
t.Fatalf("arg %d = %+v, want %v", i, a, want[i])
|
||||
}
|
||||
}
|
||||
pos, err := fbArgs([]string{"p1", "p2"}, []driver.NamedValue{{Ordinal: 1, Value: "dbo.FTV_M2M"}, {Ordinal: 2, Value: "2026-10-27T15:00:00.000"}})
|
||||
if err != nil || pos[0].Value != "dbo.FTV_M2M" || pos[1].Value != "2026-10-27 15:00:00.000" {
|
||||
t.Fatalf("позиционные: %+v %v", pos, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFBNativePassThrough(t *testing.T) {
|
||||
ddl := "CREATE OR ALTER PROCEDURE X AS BEGIN CMT = ' N' || 'ISNULL(dbo.' ; END"
|
||||
q, a, err := fbRewrite(ddl, nil)
|
||||
if err != nil || q != ddl || a != nil {
|
||||
t.Fatalf("DDL изменён: %q %v", q, err)
|
||||
}
|
||||
if !fbNative("/* шапка */\nEXECUTE BLOCK AS BEGIN END") || fbNative("EXECUTE PROCEDURE FTV_M2M_JOB") {
|
||||
t.Fatal("fbNative")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFBStatements(t *testing.T) {
|
||||
script := `/* шапка; с точкой с запятой */
|
||||
SET TERM ^ ;
|
||||
EXECUTE BLOCK AS
|
||||
BEGIN
|
||||
IF (1 = 1) THEN EXECUTE STATEMENT 'CREATE TABLE T (A VARCHAR(10) DEFAULT ''x;y'')';
|
||||
END^
|
||||
COMMIT^
|
||||
CREATE OR ALTER TRIGGER T_BI FOR T BEFORE INSERT AS BEGIN NEW.A = 'a^b'; END^
|
||||
SET TERM ; ^
|
||||
CREATE OR ALTER EXCEPTION E 'Ошибка; текст';
|
||||
-- комментарий без команды
|
||||
COMMIT;
|
||||
`
|
||||
got := fbStatements(script)
|
||||
if len(got) != 3 {
|
||||
t.Fatalf("команд %d: %q", len(got), got)
|
||||
}
|
||||
if !strings.HasPrefix(strings.TrimSpace(fbStripComments(got[0])), "EXECUTE BLOCK") || !strings.Contains(got[0], "''x;y''") {
|
||||
t.Errorf("блок: %q", got[0])
|
||||
}
|
||||
if !strings.Contains(got[1], "'a^b'") || !strings.HasPrefix(got[2], "CREATE OR ALTER EXCEPTION") {
|
||||
t.Errorf("команды: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
// Встроенные скрипты Firebird — те же, что в deploy/depo/fancy-m2m-firebird (их ставят вручную по README).
|
||||
func TestFBScriptsMatchDeploy(t *testing.T) {
|
||||
for _, n := range depoFBScriptFiles {
|
||||
b, err := readRepoFile("deploy/depo/fancy-m2m-firebird/" + n)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(b) != depoFBScript(n) {
|
||||
t.Errorf("%s: встроенный текст отличается от deploy/depo/fancy-m2m-firebird — скопируйте изменения", n)
|
||||
}
|
||||
if len(fbStatements(depoFBScript(n))) == 0 {
|
||||
t.Errorf("%s: нет команд", n)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,7 +64,7 @@ func depoIsLoginFailure(err error) bool {
|
||||
if errors.As(err, &pe) {
|
||||
return pe.Code == "28P01" || pe.Code == "28000"
|
||||
}
|
||||
return false
|
||||
return fbIsLoginFailure(err)
|
||||
}
|
||||
|
||||
// fancyConnector — подключение к DEPO через паузу после неудачного входа.
|
||||
@@ -121,5 +121,8 @@ func fancyOpenGated(s FancyMSSQLSettings, dsn string) (*sql.DB, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if s.IsFirebird() { // запросы bj (T-SQL) — в диалект Firebird
|
||||
base = fbConnector{base: base}
|
||||
}
|
||||
return sql.OpenDB(&fancyConnector{base: base, key: depoLoginKey(s), user: s.User, srv: s.Server}), nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package fsql
|
||||
|
||||
// Системные запросы к базе DEPO (MSSQL): есть ли таблица, колонки, права логина bj, процедуры.
|
||||
// Варианты для Firebird — в fb.go.
|
||||
|
||||
// Cat01 — таблица видна логину bj (@p1 — dbo.ИМЯ).
|
||||
var Cat01 = "SELECT CASE WHEN OBJECT_ID(@p1, 'U') IS NULL THEN 0 ELSE 1 END"
|
||||
|
||||
// Cat02 — колонки таблицы (@p1 — dbo.ИМЯ).
|
||||
var Cat02 = "SELECT name FROM sys.columns WHERE object_id = OBJECT_ID(@p1)"
|
||||
|
||||
// Cat03 — у логина есть право @p2 (SELECT/INSERT/UPDATE/DELETE) на таблицу @p1.
|
||||
var Cat03 = "SELECT ISNULL(HAS_PERMS_BY_NAME(@p1, 'OBJECT', @p2), 0)"
|
||||
|
||||
// Cat04 — в таблице @t есть колонка @c.
|
||||
var Cat04 = "SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME=@t AND COLUMN_NAME=@c"
|
||||
|
||||
// Cat05 — у логина есть право запускать процедуру @p.
|
||||
var Cat05 = "SELECT COUNT(*) FROM fn_my_permissions(@p,'OBJECT') WHERE permission_name='EXECUTE'"
|
||||
|
||||
// Cat06 — объект @n типа @t (U — таблица, P — процедура) есть в базе.
|
||||
var Cat06 = "SELECT CASE WHEN OBJECT_ID(@n, @t) IS NULL THEN 0 ELSE 1 END"
|
||||
|
||||
// Cat07 — пользователь базы @n (для EXECUTE AS в процедурах обработки).
|
||||
var Cat07 = "SELECT USER_ID(@n)"
|
||||
@@ -0,0 +1,71 @@
|
||||
package fsql
|
||||
|
||||
// FB — запросы к DEPO на Firebird (Fancy на Firebird 2.5, 27.10.2026): ключ — текст T-SQL, значение — текст
|
||||
// Firebird с теми же @именами параметров. Здесь только то, что подключение к Firebird не переводит само
|
||||
// (lkgateway/fancyfb.go переводит dbo., TOP, ISNULL, GETDATE(), N'…', exec): системные каталоги, LIKE с [_],
|
||||
// оконные функции, OFFSET…FETCH, DATEADD с параметром. Параметр внутри функции — с CAST: Firebird иначе не
|
||||
// знает его типа.
|
||||
var FB = map[string]string{
|
||||
Cat01: `SELECT CASE WHEN EXISTS (SELECT 1 FROM RDB$RELATIONS WHERE RDB$RELATION_NAME = UPPER(REPLACE(CAST(@p1 AS VARCHAR(63)), 'dbo.', ''))) THEN 1 ELSE 0 END FROM RDB$DATABASE`,
|
||||
|
||||
Cat02: `SELECT TRIM(RDB$FIELD_NAME) FROM RDB$RELATION_FIELDS WHERE RDB$RELATION_NAME = UPPER(REPLACE(CAST(@p1 AS VARCHAR(63)), 'dbo.', ''))`,
|
||||
|
||||
// права: SYSDBA и владелец таблицы — всё; иначе — выданное логину или PUBLIC (RDB$PRIVILEGE: S, I, U, D)
|
||||
Cat03: `SELECT CASE WHEN CURRENT_USER = 'SYSDBA'
|
||||
OR EXISTS (SELECT 1 FROM RDB$RELATIONS WHERE RDB$RELATION_NAME = UPPER(REPLACE(CAST(@p1 AS VARCHAR(63)), 'dbo.', '')) AND RDB$OWNER_NAME = CURRENT_USER)
|
||||
OR EXISTS (SELECT 1 FROM RDB$USER_PRIVILEGES WHERE RDB$USER IN (CURRENT_USER, 'PUBLIC')
|
||||
AND RDB$RELATION_NAME = UPPER(REPLACE(CAST(@p1 AS VARCHAR(63)), 'dbo.', '')) AND RDB$PRIVILEGE = SUBSTRING(CAST(@p2 AS VARCHAR(10)) FROM 1 FOR 1))
|
||||
THEN 1 ELSE 0 END FROM RDB$DATABASE`,
|
||||
|
||||
Cat04: `SELECT COUNT(*) FROM RDB$RELATION_FIELDS WHERE RDB$RELATION_NAME = UPPER(CAST(@t AS VARCHAR(63))) AND RDB$FIELD_NAME = UPPER(CAST(@c AS VARCHAR(63)))`,
|
||||
|
||||
Cat05: `SELECT COUNT(*) FROM RDB$PROCEDURES p WHERE p.RDB$PROCEDURE_NAME = UPPER(REPLACE(CAST(@p AS VARCHAR(63)), 'dbo.', ''))
|
||||
AND (CURRENT_USER = 'SYSDBA' OR p.RDB$OWNER_NAME = CURRENT_USER OR EXISTS (SELECT 1 FROM RDB$USER_PRIVILEGES u
|
||||
WHERE u.RDB$USER IN (CURRENT_USER, 'PUBLIC') AND u.RDB$RELATION_NAME = p.RDB$PROCEDURE_NAME AND u.RDB$PRIVILEGE = 'X'))`,
|
||||
|
||||
Cat06: `SELECT CASE WHEN EXISTS (SELECT 1 FROM RDB$RELATIONS WHERE RDB$RELATION_NAME = UPPER(REPLACE(CAST(@n AS VARCHAR(63)), 'dbo.', '')))
|
||||
OR EXISTS (SELECT 1 FROM RDB$PROCEDURES WHERE RDB$PROCEDURE_NAME = UPPER(REPLACE(CAST(@n AS VARCHAR(63)), 'dbo.', ''))) THEN 1 ELSE 0 END FROM RDB$DATABASE`,
|
||||
|
||||
// в Firebird нет EXECUTE AS: процедуры работают правами, выданными им самим, — пользователь не нужен
|
||||
Cat07: `SELECT 1 FROM RDB$DATABASE`,
|
||||
|
||||
QEv09: `SELECT REFERENCE FROM FTV_M2M_LINES WHERE GUID=@g AND (CAST(@i AS VARCHAR(12)) = '' OR UPPER(ISIN) = CAST(@i AS VARCHAR(12))) ORDER BY ID`,
|
||||
|
||||
QEv11: `SELECT FIRST 1 EVENT_DATE, COALESCE(STATE,''), COALESCE(COMMENT,'') FROM FTV_M2M_EVENTS
|
||||
WHERE EVENT_TYPE='HANDBOOK' AND (COMMENT IS NOT NULL OR TRIM(COALESCE(STATE,'')) <> '') ORDER BY EVENT_DATE DESC`,
|
||||
|
||||
Hlth01: `SELECT FIRST (@n) * FROM (
|
||||
SELECT CAST('FTV_M2M' AS VARCHAR(16)) AS T, ID, CAST(GUID AS VARCHAR(36)) AS G, CAST('' AS VARCHAR(36)) AS R, CAST(COALESCE(DIRECTION,'') AS VARCHAR(64)) AS K, COALESCE(TimeEnd, TimeNew) AS A, COALESCE(COMMENT,'') AS C
|
||||
FROM FTV_M2M WHERE STATE='ERROR' AND COALESCE(TimeEnd, TimeNew) > DATEADD(DAY, -CAST(@d AS INTEGER), CURRENT_TIMESTAMP)
|
||||
UNION ALL
|
||||
SELECT 'FTV_M2M_EVENTS', ID, CAST(GUID AS VARCHAR(36)), COALESCE(REFERENCE,''), EVENT_TYPE, COALESCE(TimeEnd, TimeNew), COALESCE(COMMENT,'')
|
||||
FROM FTV_M2M_EVENTS WHERE STATE='ERROR' AND EVENT_TYPE STARTING WITH 'BJ_' AND EVENT_TYPE <> 'BJ_PING' AND COALESCE(TimeEnd, TimeNew) > DATEADD(DAY, -CAST(@d AS INTEGER), CURRENT_TIMESTAMP)
|
||||
) x ORDER BY A DESC`,
|
||||
|
||||
// COUNT(*) OVER () и OFFSET…FETCH в Firebird 2.5 нет: число — подзапросом, первая строка — FIRST 1
|
||||
Hlth02: `SELECT FIRST 1 (SELECT COUNT(*) FROM FTV_M2M_EVENTS e WHERE e.STATE='ERROR' AND e.EVENT_TYPE STARTING WITH 'BJ_' AND e.ID > @last AND COALESCE(e.TimeEnd, e.TimeNew) > DATEADD(DAY, -1, CURRENT_TIMESTAMP)),
|
||||
ID, EVENT_TYPE, CAST(GUID AS VARCHAR(36)), COALESCE(COMMENT,'')
|
||||
FROM FTV_M2M_EVENTS WHERE STATE='ERROR' AND EVENT_TYPE STARTING WITH 'BJ_' AND ID > @last AND COALESCE(TimeEnd, TimeNew) > DATEADD(DAY, -1, CURRENT_TIMESTAMP)
|
||||
ORDER BY ID DESC`,
|
||||
|
||||
Hlth03: `SELECT COALESCE(MAX(ID),0) FROM FTV_M2M_EVENTS WHERE EVENT_TYPE STARTING WITH 'BJ_'`,
|
||||
|
||||
// SELECT без FROM в Firebird нет
|
||||
QEvPosted: `SELECT (SELECT COUNT(*) FROM FTV_M2M WHERE GUID=@g),
|
||||
(SELECT COUNT(*) FROM FTV_M2M_EVENTS WHERE GUID=@g AND EVENT_TYPE='BJ_ORDER_ACCEPTED' AND STATE='SUCCESS'),
|
||||
(SELECT COUNT(*) FROM FTV_M2M_EVENTS WHERE GUID=@g AND EVENT_TYPE='BJ_ORDER_ACCEPTED' AND STATE<>'SUCCESS'),
|
||||
COALESCE((SELECT FIRST 1 STATE FROM FTV_M2M WHERE GUID=@g ORDER BY ID DESC),''),
|
||||
COALESCE((SELECT FIRST 1 COMMENT FROM FTV_M2M WHERE GUID=@g AND STATE='ERROR' ORDER BY ID DESC),
|
||||
(SELECT FIRST 1 COMMENT FROM FTV_M2M_EVENTS WHERE GUID=@g AND EVENT_TYPE='BJ_ORDER_ACCEPTED' AND STATE='ERROR' ORDER BY ID DESC), '')
|
||||
FROM RDB$DATABASE`,
|
||||
|
||||
// PATINDEX в Firebird 2.5 нет: начало номера до первой цифры — по ближайшей из позиций цифр
|
||||
Acc01: `SELECT FIRST 8 p, COUNT(*) FROM (SELECT LEFT(NUMBER, MINVALUE(POSITION('0' IN NUMBER || '0'),
|
||||
COALESCE(NULLIF(POSITION('1' IN NUMBER), 0), 9999), COALESCE(NULLIF(POSITION('2' IN NUMBER), 0), 9999),
|
||||
COALESCE(NULLIF(POSITION('3' IN NUMBER), 0), 9999), COALESCE(NULLIF(POSITION('4' IN NUMBER), 0), 9999),
|
||||
COALESCE(NULLIF(POSITION('5' IN NUMBER), 0), 9999), COALESCE(NULLIF(POSITION('6' IN NUMBER), 0), 9999),
|
||||
COALESCE(NULLIF(POSITION('7' IN NUMBER), 0), 9999), COALESCE(NULLIF(POSITION('8' IN NUMBER), 0), 9999),
|
||||
COALESCE(NULLIF(POSITION('9' IN NUMBER), 0), 9999)) - 1) AS p
|
||||
FROM OD_CLIENTS WHERE STATE=0 AND D_CLOSE IS NULL AND NUMBER IS NOT NULL) x
|
||||
WHERE p <> '' GROUP BY p ORDER BY COUNT(*) DESC`,
|
||||
}
|
||||
@@ -72,6 +72,12 @@ func letterFor(s Settings, key string) (Letter, bool) {
|
||||
case "dba":
|
||||
ip := pickStr(outboundIP(s.Fancy.Server, s.Fancy.Port), firstServerIP())
|
||||
f := s.Fancy
|
||||
if f.IsFirebird() {
|
||||
return Letter{Key: key, Title: "Письмо администратору базы DEPO", To: "Администратор сервера Firebird учёта DEPO (Fancy)",
|
||||
Subject: "Bridge&Join: подготовка базы DEPO на сервере " + orBracket(f.Server, "сервер DEPO"),
|
||||
Body: fmt.Sprintf("Коллеги, добрый день!\n\nДля подключения системы Bridge&Join к учёту DEPO (Fancy на Firebird) нужно на сервере %s выполнить один скрипт %s под SYSDBA утилитой isql с кодировкой WIN1251 — команда запуска в начале скрипта (файл прилагается, он в кодировке WIN1251). Скрипт:\n— создаёт пользователя сервера Firebird для bj (%s) с паролем из скрипта; права у него — только выданные скриптом: чтение учётных таблиц, запись в таблицы обмена FTV_M2M*, запуск процедуры обработки FTV_M2M_JOB;\n— заводит этот логин в Fancy оператором-роботом (по нему Fancy нумерует документы) и кодировщик M2M, если его нет;\n— создаёт таблицы обмена FTV_M2M*, процедуры обработки FTV_M2M_* и заполняет привязки к справочникам Fancy (таблица FTV_M2M_SETTINGS); учётные данные не меняет.\nВ конце скрипт показывает привязки: пустые значения нужно вписать вручную.\nСкрипт можно запускать повторно. В нём пароль логина bj — после выполнения файл удалите.\n\nТакже нужно разрешить подключение к серверу Firebird (порт %d) с сервера Bridge&Join (адрес %s).%s",
|
||||
orBracket(f.Server, "сервер DEPO"), depoSetupFileName(f), depoSetupLogin(f), f.DefaultPort(), orBracket(ip, "IP-адрес сервера bj"), sign)}, true
|
||||
}
|
||||
what := "— создаёт задание SQL Server Agent «" + depoJobName(f) + "» для обработки обмена;\n"
|
||||
run := "под логином с правами sysadmin (SSMS или sqlcmd)"
|
||||
if f.IsPostgres() {
|
||||
|
||||
@@ -208,9 +208,9 @@ type PostgresSettings struct {
|
||||
// Fancy (очередь запросов из ЛК + ответы «крана») и журнал обмена с НРД.
|
||||
// Аутентификация — SQL-логин (SQL Server Authentication).
|
||||
type FancyMSSQLSettings struct {
|
||||
DBType string `json:"db_type"` // "mssql" | "postgres" (пусто = mssql)
|
||||
DBType string `json:"db_type"` // "mssql" | "postgres" | "firebird" (пусто = mssql)
|
||||
Server string `json:"server"` // host или host\instance
|
||||
Port int `json:"port"` // 1433 (MSSQL) / 5432 (Postgres)
|
||||
Port int `json:"port"` // 1433 (MSSQL) / 5432 (Postgres) / 3050 (Firebird)
|
||||
User string `json:"user"` // логин
|
||||
Password string `json:"password"` // пароль
|
||||
Encrypt string `json:"encrypt"` // MSSQL: disable|false|true; Postgres: sslmode
|
||||
@@ -261,6 +261,9 @@ type FancyMSSQLSettings struct {
|
||||
// IsPostgres сообщает, выбран ли PostgreSQL (иначе — MSSQL по умолчанию).
|
||||
func (s FancyMSSQLSettings) IsPostgres() bool { return s.DBType == "postgres" }
|
||||
|
||||
// IsFirebird — Fancy на Firebird (DepoDatabase — путь к файлу базы на сервере или псевдоним, fancyfb.go).
|
||||
func (s FancyMSSQLSettings) IsFirebird() bool { return s.DBType == "firebird" }
|
||||
|
||||
// DefaultPort возвращает порт по типу СУБД, если он не задан явно.
|
||||
func (s FancyMSSQLSettings) DefaultPort() int {
|
||||
if s.Port != 0 {
|
||||
@@ -269,6 +272,9 @@ func (s FancyMSSQLSettings) DefaultPort() int {
|
||||
if s.IsPostgres() {
|
||||
return 5432
|
||||
}
|
||||
if s.IsFirebird() {
|
||||
return 3050
|
||||
}
|
||||
return 1433
|
||||
}
|
||||
|
||||
|
||||
@@ -380,7 +380,7 @@ func (h *setupHandlers) renderWizard(w http.ResponseWriter, r *http.Request) {
|
||||
// Есть ли у сервера SQL Agent: у Express его нет, у PostgreSQL — тоже; тогда обработку
|
||||
// запускает сам bj. Иначе по умолчанию джобу SQL Agent создаёт bj.
|
||||
d.DepoJobDef = "bj"
|
||||
if !s.Fancy.IsPostgres() {
|
||||
if !s.Fancy.IsPostgres() && !s.Fancy.IsFirebird() {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 4*time.Second)
|
||||
d.DepoEdition, d.DepoAgent = depoServerEdition(ctx, s.Fancy)
|
||||
cancel()
|
||||
|
||||
@@ -140,12 +140,13 @@
|
||||
{{$sub := .Sub}}
|
||||
{{if eq $sub "setup"}}
|
||||
<p>bj сверяет каждый перевод с учётом DEPO и сам создаёт в нём поручения. Четыре вопроса по порядку — из ответов bj соберёт подготовку базы.</p>
|
||||
{{$job := .Settings.Fancy.DepoJobMode}}{{$pg := eq .Settings.Fancy.DBType "postgres"}}
|
||||
<form method="post" action="/admin/setup/wizard-depo/setup" id="wiz-depo" class="dq{{if $pg}} is-pg{{end}}">
|
||||
{{$job := .Settings.Fancy.DepoJobMode}}{{$pg := eq .Settings.Fancy.DBType "postgres"}}{{$fb := eq .Settings.Fancy.DBType "firebird"}}
|
||||
<form method="post" action="/admin/setup/wizard-depo/setup" id="wiz-depo" class="dq{{if $pg}} is-pg{{end}}{{if $fb}} is-fb{{end}}">
|
||||
<section class="dq-s">
|
||||
<h3 class="dq-h">Какая СУБД у DEPO</h3>
|
||||
<div class="dq-tiles">
|
||||
<label class="dq-tile"><input type="radio" name="db_type" value="mssql" {{if not $pg}}checked{{end}}><span><b>Microsoft SQL Server</b></span></label>
|
||||
<div class="dq-tiles dq-3">
|
||||
<label class="dq-tile"><input type="radio" name="db_type" value="mssql" {{if and (not $pg) (not $fb)}}checked{{end}}><span><b>Microsoft SQL Server</b></span></label>
|
||||
<label class="dq-tile"><input type="radio" name="db_type" value="firebird" {{if $fb}}checked{{end}}><span><b>Firebird</b></span></label>
|
||||
<label class="dq-tile"><input type="radio" name="db_type" value="postgres" {{if $pg}}checked{{end}}><span><b>PostgreSQL</b></span></label>
|
||||
</div>
|
||||
</section>
|
||||
@@ -154,10 +155,11 @@
|
||||
<h3 class="dq-h">Где база DEPO</h3>
|
||||
<div class="dq-grid dq-srv">
|
||||
<label>Адрес сервера<input type="text" name="server" data-need="адрес сервера DEPO" value="{{.Settings.Fancy.Server}}" placeholder="IP-адрес или имя\экземпляр" required autofocus spellcheck="false"></label>
|
||||
<label>Порт<input type="text" name="port" value="{{if .Settings.Fancy.Port}}{{.Settings.Fancy.Port}}{{else if $pg}}5432{{else}}1433{{end}}" inputmode="numeric"></label>
|
||||
<label>Имя базы<input type="text" name="depo_database" data-need="имя базы DEPO" value="{{.Settings.Fancy.DepoDatabase}}" placeholder="как называется на сервере" required pattern="[A-Za-z][A-Za-z0-9_]*" title="латинские буквы, цифры и _, начинается с буквы" spellcheck="false"></label>
|
||||
<label>Порт<input type="text" name="port" value="{{if .Settings.Fancy.Port}}{{.Settings.Fancy.Port}}{{else if $pg}}5432{{else if $fb}}3050{{else}}1433{{end}}" inputmode="numeric"></label>
|
||||
<label><span data-nofb>Имя базы</span><span data-fb>Путь к базе</span><input type="text" name="depo_database" data-need="имя базы DEPO" value="{{.Settings.Fancy.DepoDatabase}}" placeholder="{{if $fb}}D:\Fancy\DEPO.GDB или псевдоним{{else}}как называется на сервере{{end}}" required {{if not $fb}}pattern="[A-Za-z][A-Za-z0-9_]*" {{end}}title="латинские буквы, цифры и _, начинается с буквы" spellcheck="false"></label>
|
||||
</div>
|
||||
<p class="dq-note">Точное имя базы учёта DEPO — bj будет работать только с ней. Перед подготовкой bj проверит, что в ней есть учётные таблицы DEPO, и в чужую базу ничего не запишет.</p>
|
||||
<p class="dq-note" data-nofb>Точное имя базы учёта DEPO — bj будет работать только с ней. Перед подготовкой bj проверит, что в ней есть учётные таблицы DEPO, и в чужую базу ничего не запишет.</p>
|
||||
<p class="dq-note" data-fb>Путь к файлу базы учёта на сервере Firebird — как в подключении Fancy (например, <span class="dq-mono">D:\Fancy\Bases\DEPO.GDB</span>), или её псевдоним из aliases.conf. Перед подготовкой bj проверит, что это база DEPO и функции Fancy на сервере доступны.</p>
|
||||
</section>
|
||||
|
||||
<section class="dq-s">
|
||||
@@ -171,6 +173,7 @@
|
||||
<p class="dq-note">Задание называется по базе: у каждой базы DEPO на сервере — своё. Если обработку этой базы уже запускает задание, впишите его имя — второе создано не будет.</p>
|
||||
</div>
|
||||
<p class="dq-note" data-pg>Для PostgreSQL обработку запускает сервер bj — выбирать не нужно.</p>
|
||||
<p class="dq-note" data-fb>Для Firebird обработку запускает сервер bj — выбирать не нужно.</p>
|
||||
</section>
|
||||
|
||||
<section class="dq-s">
|
||||
@@ -182,7 +185,7 @@
|
||||
<label class="dq-alt"><input type="radio" name="mode" value="existing"> База уже подготовлена раньше — у bj есть свой логин</label>
|
||||
|
||||
<div class="dq-panel dq-box" id="wiz-depo-auto">
|
||||
<div class="dq-box-h">Администратор SQL <span>— нужен один раз, чтобы подготовить базу; bj его не сохраняет</span></div>
|
||||
<div class="dq-box-h"><span data-nofb>Администратор SQL</span><span data-fb>Администратор сервера Firebird (SYSDBA)</span> <span>— нужен один раз, чтобы подготовить базу; bj его не сохраняет</span></div>
|
||||
<div class="dq-grid dq-2">
|
||||
<label>Логин<input type="text" name="admin_user" data-need="логин администратора SQL" autocomplete="off" spellcheck="false" placeholder="{{if $pg}}postgres{{else}}sa{{end}}"></label>
|
||||
<label>Пароль<input type="password" name="admin_password" data-need="пароль администратора SQL" autocomplete="new-password"></label>
|
||||
@@ -197,11 +200,12 @@
|
||||
<label><span class="dq-lbl-row">Пароль <button type="button" class="dq-link" id="wiz-depo-gen">↻ другой пароль</button></span><input type="text" name="bj_password" data-need="пароль bj" value="{{.DepoPass}}" autocomplete="off" spellcheck="false" class="dq-mono"></label>
|
||||
</div>
|
||||
<p class="dq-note">bj придумал их сам. Можно заменить своими — <b>bj сохранит и будет использовать ровно то, что здесь написано</b>: подготовка создаст на сервере этот логин с этим паролем<span id="wiz-depo-login-script"> (в скрипте — они же)</span>.</p>
|
||||
<p class="dq-note" data-fb>Для Firebird логин bj начинается с SYSDBA: Fancy даёт права на документы только таким логинам. Для сервера Firebird это обычный пользователь — у него будут только права, которые выдаст подготовка: чтение учёта, таблицы обмена, запуск обработки. bj заведёт его в Fancy оператором-роботом — по нему Fancy нумерует документы. Firebird 2.5 учитывает первые 8 знаков пароля.</p>
|
||||
<p class="dq-warn">Если логин с таким именем на сервере уже есть, ему будет поставлен этот пароль — программа, которая им пользуется, перестанет входить. Тогда впишите другое имя.</p>
|
||||
</div>
|
||||
|
||||
<div class="dq-panel" id="wiz-depo-autogo">
|
||||
<p class="dq-note">bj проверит, что это база DEPO, создаст свой логин, таблицы обмена<span data-ms> и задание обработки</span>, выдаст права и проверит подключение.</p>
|
||||
<p class="dq-note">bj проверит, что это база DEPO, создаст свой логин, таблицы обмена<span data-ms> и задание обработки</span><span data-fb>, оператора Fancy, процедуры обработки</span>, выдаст права и проверит подключение.</p>
|
||||
<button type="submit" class="btn dq-go">Подготовить</button>
|
||||
</div>
|
||||
|
||||
@@ -214,7 +218,7 @@
|
||||
</div>
|
||||
<pre class="copyable" id="wiz-depo-sql" data-msg="Впишите имя базы DEPO — скрипт появится здесь."></pre>
|
||||
</div>
|
||||
<p class="dq-note"><span data-ms>Администратор выполняет скрипт в SSMS или sqlcmd под логином с правами sysadmin.</span><span data-pg>Администратор выполняет скрипт в psql суперпользователем (postgres).</span> Когда выполнит — нажмите «Проверить подключение».</p>
|
||||
<p class="dq-note"><span data-ms>Администратор выполняет скрипт в SSMS или sqlcmd под логином с правами sysadmin.</span><span data-pg>Администратор выполняет скрипт в psql суперпользователем (postgres).</span><span data-fb>Администратор выполняет скрипт утилитой isql под SYSDBA с кодировкой WIN1251 — команда в начале скрипта; файл скачивается в WIN1251.</span> Когда выполнит — нажмите «Проверить подключение».</p>
|
||||
{{template "letterCard" (letter "dba")}}
|
||||
<button type="submit" class="btn dq-go">Проверить подключение</button>
|
||||
</div>
|
||||
@@ -238,10 +242,14 @@
|
||||
var f=document.getElementById('wiz-depo'), pre=document.getElementById('wiz-depo-sql');
|
||||
var timer=0, seq=0, last='';
|
||||
function sync(){ // СУБД меняет подсказки, порт и обязательность имени базы; способ — какую панель показать
|
||||
var pg=f.db_type.value==='postgres', m=f.mode.value;
|
||||
f.classList.toggle('is-pg', pg);
|
||||
if(pg&&f.port.value==='1433') f.port.value='5432'; else if(!pg&&f.port.value==='5432') f.port.value='1433';
|
||||
f.admin_user.placeholder=pg?'postgres':'sa';
|
||||
var t=f.db_type.value, pg=t==='postgres', fb=t==='firebird', m=f.mode.value;
|
||||
f.classList.toggle('is-pg', pg); f.classList.toggle('is-fb', fb);
|
||||
if(['1433','5432','3050'].indexOf(f.port.value)>=0) f.port.value={mssql:'1433',postgres:'5432',firebird:'3050'}[t];
|
||||
f.admin_user.placeholder=pg?'postgres':fb?'SYSDBA':'sa';
|
||||
var dbi=f.depo_database; // Firebird: путь к файлу базы или псевдоним, а не имя
|
||||
if(fb){ dbi.removeAttribute('pattern'); dbi.placeholder='D:\\Fancy\\DEPO.GDB или псевдоним'; }
|
||||
else { dbi.setAttribute('pattern','[A-Za-z][A-Za-z0-9_]*'); dbi.placeholder='как называется на сервере'; }
|
||||
if(fb&&f.bj_user.value==='bj_user') f.bj_user.value='SYSDBA_BJ'; else if(!fb&&f.bj_user.value==='SYSDBA_BJ') f.bj_user.value='bj_user';
|
||||
var jn=f.depo_job_name, dbv=(f.depo_database.value||'').trim(); // имя задания — по базе, пока не поправили руками
|
||||
if(jn.dataset.auto==='1') jn.value=dbv?'bj2depo_'+dbv:'';
|
||||
document.getElementById('wiz-depo-jobbox').hidden=f.depo_job.value!=='agent';
|
||||
|
||||
@@ -535,7 +535,8 @@ details:has(> pre.copyable) { min-width:0; max-width:100%; }
|
||||
.dq-sql pre:empty::before { content:attr(data-msg); color:var(--muted); font-family:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif; font-size:13px; }
|
||||
.dq-sql pre { max-height:380px; min-height:140px; font-size:12px; margin:0; transition:opacity .2s; }
|
||||
.dq-sql pre.dq-busy { opacity:.5; }
|
||||
.dq.is-pg [data-ms], .dq:not(.is-pg) [data-pg] { display:none !important; }
|
||||
.dq.is-pg [data-ms], .dq:not(.is-pg) [data-pg], .dq.is-fb [data-ms], .dq:not(.is-fb) [data-fb], .dq.is-fb [data-nofb] { display:none !important; }
|
||||
.dq-tiles.dq-3 { grid-template-columns:repeat(3,minmax(0,1fr)); }
|
||||
.dq-jobname { margin-top:14px; max-width:520px; }
|
||||
.dq-jobname label { display:block; margin:0; font-size:13.5px; font-weight:500; color:var(--text-2); }
|
||||
.dq-jobname input { display:block; width:100%; box-sizing:border-box; margin-top:5px; }
|
||||
@@ -568,7 +569,7 @@ button.dq-link, .dq-link { background:none !important; border:0 !important; padd
|
||||
.dq-run-msg { margin-top:8px; padding:10px 12px; border-radius:8px; background:var(--err-weak, rgba(200,40,40,.08)); font-size:13.5px; overflow-wrap:anywhere; white-space:pre-wrap; }
|
||||
@keyframes dq-spin { to { transform:rotate(360deg); } }
|
||||
@media (prefers-reduced-motion: reduce) { .dq-st.is-run .dq-st-ic { animation:none; border-color:var(--accent); } }
|
||||
@media (max-width:600px) { .dq-tiles, .dq-srv, .dq-2 { grid-template-columns:minmax(0,1fr); } .dq-sql-head { flex-wrap:wrap; } .dq-sql-st { flex-basis:100%; } }
|
||||
@media (max-width:600px) { .dq-tiles, .dq-tiles.dq-3, .dq-srv, .dq-2 { grid-template-columns:minmax(0,1fr); } .dq-sql-head { flex-wrap:wrap; } .dq-sql-st { flex-basis:100%; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
{{define "fancySection"}}
|
||||
<div class="card" id="sec-fancy-conn">
|
||||
<h2><span class="dot {{if .Settings.Fancy.DepoTablesCheckOK}}ok{{else if .Settings.Fancy.Server}}warn{{else}}err{{end}}"></span>Подключение к серверу БД клиента</h2>
|
||||
<p class="muted">Сервер учётной базы Fancy DEPO: картотека лиц, счета и договоры, остатки, опции (признак квалифицированного инвестора), трансферные таблицы обмена. Поддерживаются <strong>Microsoft SQL Server</strong> и <strong>PostgreSQL</strong>. База указывается ниже, в карточке «Трансферные таблицы DEPO».</p>
|
||||
<p class="muted">Сервер учётной базы Fancy DEPO: картотека лиц, счета и договоры, остатки, опции (признак квалифицированного инвестора), трансферные таблицы обмена. Поддерживаются <strong>Microsoft SQL Server</strong>, <strong>Firebird</strong> (Fancy на Firebird 2.5) и <strong>PostgreSQL</strong>. База указывается ниже, в карточке «Трансферные таблицы DEPO»; у Firebird — путь к файлу базы на сервере или псевдоним.</p>
|
||||
<form method="post" action="/admin/setup/fancy" style="margin-top:12px;display:grid;gap:10px;max-width:560px">
|
||||
<label>Тип СУБД
|
||||
<select name="db_type" id="fancy-dbtype" style="width:100%" onchange="var p=document.getElementById('fancy-port');if(!p.value||p.value=='1433'||p.value=='5432'){p.value=this.value=='postgres'?'5432':'1433';}">
|
||||
<option value="mssql" {{if not .Settings.Fancy.IsPostgres}}selected{{end}}>Microsoft SQL Server</option>
|
||||
<select name="db_type" id="fancy-dbtype" style="width:100%" onchange="var p=document.getElementById('fancy-port');if(!p.value||p.value=='1433'||p.value=='5432'||p.value=='3050'){p.value={mssql:'1433',postgres:'5432',firebird:'3050'}[this.value];}">
|
||||
<option value="mssql" {{if and (not .Settings.Fancy.IsPostgres) (not .Settings.Fancy.IsFirebird)}}selected{{end}}>Microsoft SQL Server</option>
|
||||
<option value="firebird" {{if .Settings.Fancy.IsFirebird}}selected{{end}}>Firebird</option>
|
||||
<option value="postgres" {{if .Settings.Fancy.IsPostgres}}selected{{end}}>PostgreSQL</option>
|
||||
</select></label>
|
||||
<label>Сервер (host или host\instance)
|
||||
@@ -30,7 +31,7 @@
|
||||
|
||||
<div class="card" id="sec-fancy-depo-tables">
|
||||
<h2><span class="dot {{if .Settings.Fancy.DepoTablesCheckOK}}ok{{else if .Settings.Fancy.Server}}warn{{else}}err{{end}}"></span>Трансферные таблицы DEPO</h2>
|
||||
<p class="muted">Через эти таблицы bj обменивается с учётной системой DEPO: поручения <code>FTV_M2M</code> и их строки <code>FTV_M2M_LINES</code>, события <code>FTV_M2M_EVENTS</code>, справочник участников НРД <code>FTV_M2M_HANDBOOK</code>. Таблицы создаются в базе DEPO на сервере из подключения выше ({{if .DepoTables.Postgres}}PostgreSQL{{else}}Microsoft SQL Server{{end}}). Создание можно повторять: добавляются только недостающие таблицы, колонки и индексы, данные не меняются.</p>
|
||||
<p class="muted">Через эти таблицы bj обменивается с учётной системой DEPO: поручения <code>FTV_M2M</code> и их строки <code>FTV_M2M_LINES</code>, события <code>FTV_M2M_EVENTS</code>, справочник участников НРД <code>FTV_M2M_HANDBOOK</code>. Таблицы создаются в базе DEPO на сервере из подключения выше ({{if .DepoTables.Postgres}}PostgreSQL{{else if .DepoTables.Firebird}}Firebird{{else}}Microsoft SQL Server{{end}}). Создание можно повторять: добавляются только недостающие таблицы, колонки и индексы, данные не меняются.</p>
|
||||
<form method="post" action="/admin/setup/fancy/depo-tables" style="margin-top:10px;display:grid;gap:10px;max-width:720px">
|
||||
<input type="hidden" name="section" value="sec-fancy-depo-tables">
|
||||
<label>База DEPO
|
||||
@@ -46,9 +47,9 @@
|
||||
<form method="post" action="/admin/setup/fancy/depo-tables/create" style="margin-top:12px;display:grid;gap:10px;max-width:720px">
|
||||
<input type="hidden" name="section" value="sec-fancy-depo-tables">
|
||||
<div><b>Логин с правами на базу {{.DepoTables.DB}}</b><br>
|
||||
<span class="muted" style="font-size:12px">{{if .DepoTables.Postgres}}Владелец базы или суперпользователь.{{else}}Роль db_owner в базе (или db_ddladmin + db_securityadmin).{{end}} Если оставить поля пустыми, bj попробует своим логином {{.DepoTables.User}}.</span></div>
|
||||
<span class="muted" style="font-size:12px">{{if .DepoTables.Postgres}}Владелец базы или суперпользователь.{{else if .DepoTables.Firebird}}SYSDBA: таблицы ставятся вместе с привязками, процедурами и правами.{{else}}Роль db_owner в базе (или db_ddladmin + db_securityadmin).{{end}} Если оставить поля пустыми, bj попробует своим логином {{.DepoTables.User}}.</span></div>
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:10px">
|
||||
<label>Логин <input type="text" name="admin_user" autocomplete="off" placeholder="{{if .DepoTables.Postgres}}postgres{{else}}sa{{end}}" style="width:100%"></label>
|
||||
<label>Логин <input type="text" name="admin_user" autocomplete="off" placeholder="{{if .DepoTables.Postgres}}postgres{{else if .DepoTables.Firebird}}SYSDBA{{else}}sa{{end}}" style="width:100%"></label>
|
||||
<label>Пароль <input type="password" name="admin_password" autocomplete="new-password" style="width:100%"></label>
|
||||
</div>
|
||||
<div><button type="submit" class="btn btn-ok" onclick="return confirm('Создать трансферные таблицы в базе {{.DepoTables.DB}}?')">⚙ Создать таблицы</button></div>
|
||||
@@ -150,7 +151,7 @@
|
||||
{{if not $f.Server}}<a class="btn" href="/admin/wizard?step=3" style="flex:none">Открыть мастер</a>{{end}}</div>
|
||||
{{if $f.Server}}
|
||||
<dl class="kv">
|
||||
<dt>База DEPO</dt><dd><b>{{$f.DepoDB}}</b> на {{$f.Server}}{{if $f.Port}}:{{$f.Port}}{{end}} · {{if eq $f.DBType "postgres"}}PostgreSQL{{else}}Microsoft SQL Server{{end}}</dd>
|
||||
<dt>База DEPO</dt><dd><b>{{$f.DepoDB}}</b> на {{$f.Server}}{{if $f.Port}}:{{$f.Port}}{{end}} · {{if eq $f.DBType "postgres"}}PostgreSQL{{else if eq $f.DBType "firebird"}}Firebird{{else}}Microsoft SQL Server{{end}}</dd>
|
||||
<dt>Логин bj</dt><dd><code>{{$f.User}}</code></dd>
|
||||
<dt>Торговые счета клиентов</dt><dd>номер начинается с <code>{{depoAccPrefix}}</code></dd>
|
||||
<dt>Признак ИИС</dt><dd>договор начинается с <code>{{if $f.IISContractMarker}}{{$f.IISContractMarker}}{{else}}БРИИС{{end}}</code> — такие переводы отклоняются кодом M2M06</dd>
|
||||
|
||||
@@ -219,12 +219,12 @@ func (h *setupHandlers) wizardDepoRecheck(w http.ResponseWriter, r *http.Request
|
||||
func (h *setupHandlers) depoSetupForm(r *http.Request) (FancyMSSQLSettings, string, bool, string) {
|
||||
s := h.rc.Snapshot().Fancy
|
||||
dbType := "mssql"
|
||||
if r.FormValue("db_type") == "postgres" {
|
||||
dbType = "postgres"
|
||||
if v := r.FormValue("db_type"); v == "postgres" || v == "firebird" {
|
||||
dbType = v
|
||||
}
|
||||
port, _ := strconv.Atoi(strings.TrimSpace(r.FormValue("port")))
|
||||
if port == 0 {
|
||||
port = map[string]int{"mssql": 1433, "postgres": 5432}[dbType]
|
||||
port = map[string]int{"mssql": 1433, "postgres": 5432, "firebird": 3050}[dbType]
|
||||
}
|
||||
server := strings.TrimSpace(r.FormValue("server"))
|
||||
if server != s.Server || dbType != s.DBType {
|
||||
@@ -235,15 +235,24 @@ func (h *setupHandlers) depoSetupForm(r *http.Request) (FancyMSSQLSettings, stri
|
||||
if s.Encrypt == "" {
|
||||
s.Encrypt = "disable"
|
||||
}
|
||||
agent := r.FormValue("depo_job") != "bj" && dbType != "postgres"
|
||||
agent := r.FormValue("depo_job") != "bj" && dbType == "mssql" // у PostgreSQL и Firebird нет SQL Server Agent
|
||||
s.DepoJobMode, s.DepoJobRun = "bj", !agent
|
||||
if agent {
|
||||
s.DepoJobMode = "sql-auto"
|
||||
}
|
||||
db := strings.TrimSpace(r.FormValue("depo_database"))
|
||||
if db == "" { // bj по серверу базу не ищет: с какой базой работать, клиент говорит сам
|
||||
if dbType == "firebird" {
|
||||
return s, "", agent, "укажите путь к базе DEPO на сервере Firebird — как в настройке подключения Fancy"
|
||||
}
|
||||
return s, "", agent, "укажите имя базы DEPO — как она называется на сервере"
|
||||
}
|
||||
if dbType == "firebird" {
|
||||
if !validFBPath(db) {
|
||||
return s, "", agent, "путь к базе DEPO — без кавычек, ; ? и #"
|
||||
}
|
||||
return s, db, agent, ""
|
||||
}
|
||||
if !validDBName(db) {
|
||||
return s, "", agent, "имя базы DEPO — только латинские буквы, цифры и _, начинается с буквы"
|
||||
}
|
||||
@@ -274,7 +283,12 @@ func depoEnsureLogin(s *FancyMSSQLSettings) {
|
||||
// пароль) — тогда вписывают другое имя. Пустой пароль — bj придумает сам. Возвращает текст ошибки.
|
||||
func depoBjCreds(r *http.Request, s *FancyMSSQLSettings) string {
|
||||
if u := strings.TrimSpace(r.FormValue("bj_user")); u != "" {
|
||||
if !depoLoginRe.MatchString(u) {
|
||||
if s.IsFirebird() {
|
||||
if !fbLoginRe.MatchString(u) {
|
||||
return "логин bj для Firebird начинается с SYSDBA (например, " + depoFBDefaultLogin + "), дальше — латинские буквы, цифры и _: Fancy даёт права на документы только логинам SYSDBA…"
|
||||
}
|
||||
u = strings.ToUpper(u)
|
||||
} else if !depoLoginRe.MatchString(u) {
|
||||
return "логин bj — латинские буквы, цифры, точка, дефис и _"
|
||||
}
|
||||
if u != s.User {
|
||||
@@ -323,7 +337,7 @@ func (h *setupHandlers) wizardDepoScript(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
}
|
||||
body := depoSetupScript(s, db, s.User, s.Password, agent)
|
||||
if !s.IsPostgres() { // процедуры обработки bj2depo — тем же файлом
|
||||
if !s.IsPostgres() && !s.IsFirebird() { // процедуры обработки bj2depo — тем же файлом (у Firebird — уже в скрипте)
|
||||
body += "\nGO\n" + depoProcsScriptFor(s, db, s.User)
|
||||
}
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
@@ -332,6 +346,12 @@ func (h *setupHandlers) wizardDepoScript(w http.ResponseWriter, r *http.Request)
|
||||
_, _ = w.Write([]byte(body))
|
||||
return
|
||||
}
|
||||
if s.IsFirebird() { // isql -ch WIN1251: файл — в WIN1251, как база Fancy
|
||||
w.Header().Set("Content-Type", "application/sql; charset=windows-1251")
|
||||
w.Header().Set("Content-Disposition", `attachment; filename="`+depoSetupFileName(s)+`"`)
|
||||
_, _ = w.Write(win1251(body))
|
||||
return
|
||||
}
|
||||
if !s.IsPostgres() {
|
||||
body = "\uFEFF" + body // SSMS без BOM читает кириллицу в комментариях как ANSI
|
||||
}
|
||||
@@ -413,7 +433,11 @@ func (h *setupHandlers) depoSetupDo(ctx context.Context, r *http.Request, step d
|
||||
if err := depoSetupRun(ctx, s, db, au, r.FormValue("admin_password"), s.User, s.Password, agent, step); err != nil {
|
||||
return fail("база не подготовлена — " + err.Error())
|
||||
}
|
||||
if !s.IsPostgres() { // процедуры обработки bj2depo (CREATE OR ALTER, с учётом правки под клиента)
|
||||
if s.IsFirebird() { // процедуры и права поставил depoFBSetup — плашка обновления процедур не нужна
|
||||
s.DepoDatabase = db
|
||||
s.DepoProcsApplied, s.DepoProcsAppliedAt = depoProcsHash(s), time.Now()
|
||||
}
|
||||
if !s.IsPostgres() && !s.IsFirebird() { // процедуры обработки bj2depo (CREATE OR ALTER, с учётом правки под клиента)
|
||||
step("procs", "run", "Устанавливаю процедуры обработки bj2depo")
|
||||
adm := s
|
||||
adm.User, adm.Password = au, r.FormValue("admin_password")
|
||||
@@ -424,6 +448,9 @@ func (h *setupHandlers) depoSetupDo(ctx context.Context, r *http.Request, step d
|
||||
}
|
||||
s.DepoTablesMode = "auto"
|
||||
note = "база " + db + " подготовлена: логин " + s.User + ", таблицы обмена, права"
|
||||
if s.IsFirebird() {
|
||||
note += ", оператор Fancy и процедуры обработки"
|
||||
}
|
||||
if agent {
|
||||
note += ", задание " + depoJobName(s)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user