Files
Bridge-and-Join-s/internal/lkgateway/depo_setup_fb.go
fontvielle 50e420f4a0 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
2026-10-27 18:55:02 +03:00

465 lines
23 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package lkgateway
// Подготовка базы 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
}