DEPO: процедуры ставятся в мастере «Подготовить» + правка под клиента
- «Подготовить» в мастере (step 3, режим «дать логин администратора») теперь ставит и процедуры обработки bj2depo (CREATE OR ALTER) + GRANT, не только логин/таблицы/джобу. - Скачиваемый скрипт мастера включает процедуры. - Тексты процедур редактируются под клиента (Настройка → Обмен DEPO → Процедуры): сохранённый вариант используется при установке и при «Подготовить». Пусто/совпадение со встроенным — исходная версия. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn
This commit is contained in:
@@ -101,6 +101,8 @@ var templateFuncs = template.FuncMap{
|
||||
"whForm": func(w WorkHoursSettings, ret string) map[string]any { return map[string]any{"W": w, "Ret": ret} },
|
||||
"weekdayNames": func() []string { return weekdayShort },
|
||||
"depoAccPrefix": depoAccPrefix,
|
||||
"depoProcText": depoProcEffective,
|
||||
"depoProcList": depoProcList,
|
||||
"certDaysLeft": certDaysLeft,
|
||||
"currentContour": func() string { return contourOf(currentNSDProfile()) },
|
||||
"httpsCert": func() map[string]any {
|
||||
|
||||
@@ -43,7 +43,7 @@ func splitSQLBatches(s string) []string {
|
||||
return out
|
||||
}
|
||||
|
||||
// depoProcFile — тело встроенной процедуры.
|
||||
// depoProcFile — тело встроенной (исходной) процедуры из файла.
|
||||
func depoProcFile(file string) (string, error) {
|
||||
b, err := depoProcFS.ReadFile(file)
|
||||
if err != nil {
|
||||
@@ -52,25 +52,83 @@ func depoProcFile(file string) (string, error) {
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
// embeddedProcBody — встроенный текст процедуры по имени.
|
||||
func embeddedProcBody(name string) string {
|
||||
for _, p := range depoProcInstallOrder {
|
||||
if p.Name == name {
|
||||
b, _ := depoProcFile(p.File)
|
||||
return b
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// depoProcEffective — текст процедуры с учётом правки под клиента: сохранённый
|
||||
// в настройках вариант, иначе встроенный. Используется в предпросмотре, установке
|
||||
// и скачиваемом скрипте.
|
||||
func depoProcEffective(f FancyMSSQLSettings, name string) string {
|
||||
if v, ok := f.DepoProcOverrides[name]; ok && strings.TrimSpace(v) != "" {
|
||||
return v
|
||||
}
|
||||
return embeddedProcBody(name)
|
||||
}
|
||||
|
||||
// depoProcList — порядок процедур для шаблона (редактор текстов).
|
||||
func depoProcList() []struct{ Name, File string } { return depoProcInstallOrder }
|
||||
|
||||
// depoProcsCombined — единый скрипт установки (для скачивания администратору): USE, процедуры, GRANT.
|
||||
func depoProcsCombined(f FancyMSSQLSettings) string {
|
||||
return depoProcsScriptFor(f, f.DepoDB(), depoSetupLogin(f))
|
||||
}
|
||||
|
||||
// depoProcsScriptFor — скрипт установки процедур в базу db с выдачей EXECUTE логину login.
|
||||
// Тексты процедур — с учётом правки под клиента (depoProcEffective).
|
||||
func depoProcsScriptFor(f FancyMSSQLSettings, db, login string) string {
|
||||
var b strings.Builder
|
||||
login := depoSetupLogin(f)
|
||||
b.WriteString("/* Bridge&Join: установка процедур обработки bj2depo в базу DEPO.\n")
|
||||
b.WriteString("/* Bridge&Join: процедуры обработки bj2depo для базы DEPO.\n")
|
||||
b.WriteString(" Порядок WIRING -> OPERATION -> EVENTS -> CREATE -> JOB. Запуск под sysadmin в SSMS.\n")
|
||||
b.WriteString(" CREATE OR ALTER — повторный запуск безопасен (данные не меняются). */\n")
|
||||
fmt.Fprintf(&b, "USE %s;\nGO\n\n", quoteIdent(f.DepoDB()))
|
||||
fmt.Fprintf(&b, "USE %s;\nGO\n\n", quoteIdent(db))
|
||||
for _, p := range depoProcInstallOrder {
|
||||
body, err := depoProcFile(p.File)
|
||||
if err != nil {
|
||||
body := depoProcEffective(f, p.Name)
|
||||
if strings.TrimSpace(body) == "" {
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(&b, "/* ===== %s ===== */\n%s\nGO\n\n", p.Name, strings.TrimRight(body, "\n"))
|
||||
}
|
||||
fmt.Fprintf(&b, "/* Право запуска задания логину bj */\nGRANT EXECUTE ON dbo.FTV_M2M_JOB TO %s;\nGO\n", quoteIdent(login))
|
||||
if depoLoginRe.MatchString(login) {
|
||||
fmt.Fprintf(&b, "/* Право запуска задания логину bj */\nGRANT EXECUTE ON dbo.FTV_M2M_JOB TO %s;\nGO\n", quoteIdent(login))
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// depoProcsInstallInto — установить процедуры (CREATE OR ALTER, с учётом правки под клиента) в базу
|
||||
// dbName соединением adm (логин администратора) и выдать EXECUTE логину grantLogin.
|
||||
func depoProcsInstallInto(ctx context.Context, adm, f FancyMSSQLSettings, dbName, grantLogin string) error {
|
||||
db, err := fancyOpen(adm, dbName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
for _, p := range depoProcInstallOrder {
|
||||
body := depoProcEffective(f, p.Name)
|
||||
if strings.TrimSpace(body) == "" {
|
||||
return fmt.Errorf("пустой текст процедуры %s", p.Name)
|
||||
}
|
||||
for _, batch := range splitSQLBatches(body) {
|
||||
if _, err := db.ExecContext(ctx, batch); err != nil {
|
||||
return fmt.Errorf("установка %s: %s", p.Name, quikErrorDetail(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
if depoLoginRe.MatchString(grantLogin) {
|
||||
if _, err := db.ExecContext(ctx, "GRANT EXECUTE ON dbo.FTV_M2M_JOB TO "+quoteIdent(grantLogin)); err != nil {
|
||||
return fmt.Errorf("право EXECUTE логину %s: %s", grantLogin, quikErrorDetail(err))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// quoteIdent — квадратные скобки для идентификатора T-SQL.
|
||||
func quoteIdent(s string) string { return "[" + strings.ReplaceAll(s, "]", "]]") + "]" }
|
||||
|
||||
@@ -137,35 +195,15 @@ func depoProcsInstall(ctx context.Context, f FancyMSSQLSettings, adminUser, admi
|
||||
if !canApply {
|
||||
return "", fmt.Errorf("нет прав на установку. %s", report)
|
||||
}
|
||||
conn := f
|
||||
adm := f
|
||||
if u := strings.TrimSpace(adminUser); u != "" {
|
||||
conn.User, conn.Password = u, adminPass
|
||||
adm.User, adm.Password = u, adminPass
|
||||
}
|
||||
db, err := fancyOpen(conn, f.DepoDB())
|
||||
if err != nil {
|
||||
login := depoSetupLogin(f)
|
||||
if err := depoProcsInstallInto(ctx, adm, f, f.DepoDB(), login); err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer db.Close()
|
||||
for _, p := range depoProcInstallOrder {
|
||||
body, err := depoProcFile(p.File)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("встроенный файл %s: %w", p.File, err)
|
||||
}
|
||||
for _, batch := range splitSQLBatches(body) {
|
||||
if _, err := db.ExecContext(ctx, batch); err != nil {
|
||||
return "", fmt.Errorf("установка %s: %s", p.Name, quikErrorDetail(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
msg := "установлено процедур: 5 (WIRING, OPERATION, EVENTS, CREATE, JOB)"
|
||||
login := depoSetupLogin(f)
|
||||
if depoLoginRe.MatchString(login) {
|
||||
if _, err := db.ExecContext(ctx, "GRANT EXECUTE ON dbo.FTV_M2M_JOB TO "+quoteIdent(login)); err != nil {
|
||||
msg += "; право EXECUTE логину " + login + " не выдано: " + quikErrorDetail(err)
|
||||
} else {
|
||||
msg += "; право EXECUTE логину " + login + " выдано"
|
||||
}
|
||||
}
|
||||
msg := "установлено процедур: 5 (WIRING, OPERATION, EVENTS, CREATE, JOB); право EXECUTE логину " + login + " выдано"
|
||||
log.Printf("depo-procs: %s", msg)
|
||||
return msg, nil
|
||||
}
|
||||
@@ -237,3 +275,35 @@ func (h *setupHandlers) fancyDepoProcsScript(w http.ResponseWriter, r *http.Requ
|
||||
// UTF-8 BOM (0xEF 0xBB 0xBF): SSMS без него читает кириллицу в комментариях как ANSI.
|
||||
_, _ = w.Write(append([]byte{0xEF, 0xBB, 0xBF}, depoProcsCombined(s)...))
|
||||
}
|
||||
|
||||
// saveFancyDepoProcsText — POST /admin/setup/fancy/depo-procs/save-text: сохранить правку текстов процедур
|
||||
// под клиента. Пустое поле или совпадение со встроенным текстом — вернуть встроенную версию (убрать правку).
|
||||
func (h *setupHandlers) saveFancyDepoProcsText(w http.ResponseWriter, r *http.Request) {
|
||||
if !requireDepo(w, r) {
|
||||
return
|
||||
}
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "method", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
s := h.rc.Snapshot().Fancy
|
||||
ov := map[string]string{}
|
||||
changed := 0
|
||||
for _, p := range depoProcInstallOrder {
|
||||
v := strings.ReplaceAll(r.FormValue("proc_"+p.Name), "\r\n", "\n")
|
||||
if strings.TrimSpace(v) != "" && strings.TrimSpace(v) != strings.TrimSpace(embeddedProcBody(p.Name)) {
|
||||
ov[p.Name] = v
|
||||
changed++
|
||||
}
|
||||
}
|
||||
if changed == 0 {
|
||||
s.DepoProcOverrides = nil
|
||||
} else {
|
||||
s.DepoProcOverrides = ov
|
||||
}
|
||||
if err := h.rc.UpdateFancy(s); err != nil {
|
||||
setupFlash(w, r, "Тексты процедур: ошибка сохранения — "+err.Error())
|
||||
return
|
||||
}
|
||||
setupFlash(w, r, fmt.Sprintf("Тексты процедур сохранены. Изменённых под клиента: %d (пустые или совпадающие со встроенными — используется встроенная версия).", changed))
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ func (h *setupHandlers) registerFancy(mux *http.ServeMux) {
|
||||
mux.HandleFunc("/admin/setup/fancy/depo-procs/check", h.checkFancyDepoProcs)
|
||||
mux.HandleFunc("/admin/setup/fancy/depo-procs/install", h.installFancyDepoProcs)
|
||||
mux.HandleFunc("/admin/setup/fancy/depo-procs/script.sql", h.fancyDepoProcsScript)
|
||||
mux.HandleFunc("/admin/setup/fancy/depo-procs/save-text", h.saveFancyDepoProcsText)
|
||||
mux.HandleFunc("/admin/fancy-queue", h.fancyQueue)
|
||||
mux.HandleFunc("/admin/fancy-queue/requeue", h.fancyQueueRequeue)
|
||||
mux.HandleFunc("/admin/fancy-queue/dismiss", h.fancyQueueDismiss)
|
||||
|
||||
@@ -250,6 +250,8 @@ type FancyMSSQLSettings struct {
|
||||
DepoJobName string `json:"depo_job_name,omitempty"`
|
||||
DepoNewUser string `json:"depo_new_user,omitempty"`
|
||||
DepoNewPass string `json:"depo_new_password,omitempty"`
|
||||
// DepoProcOverrides — правка текстов процедур bj2depo под клиента (имя процедуры -> SQL); пусто = встроенный текст.
|
||||
DepoProcOverrides map[string]string `json:"depo_proc_overrides,omitempty"`
|
||||
}
|
||||
|
||||
// IsPostgres сообщает, выбран ли PostgreSQL (иначе — MSSQL по умолчанию).
|
||||
|
||||
@@ -130,6 +130,12 @@
|
||||
</div>
|
||||
</form>
|
||||
<p style="margin-top:8px"><a href="/admin/setup/fancy/depo-procs/script.sql">Скачать скрипт bj-depo-procs-mssql.sql</a> <span class="muted" style="font-size:12px">— для ручной установки администратором в SSMS</span></p>
|
||||
<details style="margin-top:10px"><summary class="muted" style="cursor:pointer">Тексты процедур — изменить под клиента</summary>
|
||||
<form method="post" action="/admin/setup/fancy/depo-procs/save-text" style="display:grid;gap:10px;margin-top:10px">
|
||||
<input type="hidden" name="section" value="sec-fancy-depo-procs">
|
||||
{{range depoProcList}}<label style="font-weight:600;font-size:13px">{{.Name}}<textarea name="proc_{{.Name}}" rows="10" spellcheck="false" style="width:100%;font-family:ui-monospace,monospace;font-size:11.5px">{{depoProcText $.Settings.Fancy .Name}}</textarea></label>{{end}}
|
||||
<div><button type="submit" class="btn">Сохранить тексты</button> <span class="muted" style="font-size:12px">пустое поле или совпадение со встроенным — вернуть исходную версию; изменённые ставятся при «Установить» и при «Подготовить» в мастере</span></div>
|
||||
</form></details>
|
||||
{{if .Settings.Fancy.DepoProcCheck}}<p style="margin-top:6px;font-size:13px;{{if .Settings.Fancy.DepoProcCheckOK}}color:var(--ok){{else}}color:var(--warn){{end}}">{{if .Settings.Fancy.DepoProcCheckOK}}✓{{else}}⚠{{end}} {{.Settings.Fancy.DepoProcCheck}}</p>{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
@@ -323,6 +323,9 @@ func (h *setupHandlers) wizardDepoScript(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
}
|
||||
body := depoSetupScript(s, db, s.User, s.Password, agent)
|
||||
if !s.IsPostgres() { // процедуры обработки bj2depo — тем же файлом
|
||||
body += "\nGO\n" + depoProcsScriptFor(s, db, s.User)
|
||||
}
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
if view {
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
@@ -410,6 +413,15 @@ 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, с учётом правки под клиента)
|
||||
step("procs", "run", "Устанавливаю процедуры обработки bj2depo")
|
||||
adm := s
|
||||
adm.User, adm.Password = au, r.FormValue("admin_password")
|
||||
if err := depoProcsInstallInto(ctx, adm, s, db, s.User); err != nil {
|
||||
return fail("процедуры обработки не установлены — " + err.Error())
|
||||
}
|
||||
step("procs", "ok", "")
|
||||
}
|
||||
s.DepoTablesMode = "auto"
|
||||
note = "база " + db + " подготовлена: логин " + s.User + ", таблицы обмена, права"
|
||||
if agent {
|
||||
|
||||
Reference in New Issue
Block a user