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:
fontvielle
2026-10-27 18:55:02 +03:00
parent 7073ce2a33
commit 50e420f4a0
30 changed files with 2698 additions and 71 deletions
+464
View File
@@ -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
}