diff --git a/internal/lkgateway/depoevents.go b/internal/lkgateway/depoevents.go index f1d7f65..d53b15e 100644 --- a/internal/lkgateway/depoevents.go +++ b/internal/lkgateway/depoevents.go @@ -11,7 +11,9 @@ package lkgateway import ( "context" + "crypto/sha256" "database/sql" + "encoding/hex" "encoding/json" "errors" "fmt" @@ -20,6 +22,7 @@ import ( "os" "path/filepath" "regexp" + "sort" "strconv" "strings" "sync" @@ -660,6 +663,27 @@ func depoWriteHandbookCtx(pctx context.Context, rc *RuntimeConfig) error { log.Printf("depo-events: таблица FTV_M2M_HANDBOOK создана в %s", rc.Snapshot().Fancy.DepoDB()) } hbAt, _ := time.Parse(time.RFC3339, hb.At) + // Без изменений — базу не трогаем: тот же состав (отпечаток без даты справочника) уже записан в эту базу + // и строк в таблице столько же. Событие HANDBOOK, не записанное в прошлый раз, — дописываем одно. + sum := depoHandbookSum(hb.Participants, hb.Places) + target := rc.Snapshot().Fancy.Server + "/" + rc.Snapshot().Fancy.DepoDB() + if st := depoHandbookStateLoad(); st.Sum == sum && st.Target == target { + var rows int + if db.QueryRowContext(ctx, "SELECT COUNT(*) FROM dbo.FTV_M2M_HANDBOOK").Scan(&rows) == nil && rows == len(hb.Participants)+len(hb.Places) { + depoHandbookWritten = time.Now() + if st.Event { + return nil + } + if _, err := db.ExecContext(ctx, fsql.QEv10); err != nil { + log.Printf("depo-events: справочник без изменений, событие HANDBOOK по-прежнему не записывается: %v — примените скрипты DEPO", err) + return nil + } + st.Event = true + depoHandbookStateSave(st) + log.Printf("depo-events: справочник без изменений; записано отложенное событие HANDBOOK") + return nil + } + } tx, err := db.BeginTx(ctx, nil) if err != nil { return err @@ -706,9 +730,11 @@ func depoWriteHandbookCtx(pctx context.Context, rc *RuntimeConfig) error { // переносит справочник в юрлица Fancy (FTV_M2M_UPDATE_HANDBOOK). Отдельно от записи справочника: не // записалось (в старой таблице GUID NOT NULL) — справочник всё равно в DEPO, а событие ждёт скриптов. if _, err := db.ExecContext(ctx, fsql.QEv10); err != nil { + depoHandbookStateSave(depoHandbookState{Sum: sum, Target: target, Event: false}) log.Printf("depo-events: справочник записан в FTV_M2M_HANDBOOK (%d строк), но событие HANDBOOK — нет: %v — примените скрипты DEPO (плашка «Обновление bj требует применить скрипты»)", n, err) return nil } + depoHandbookStateSave(depoHandbookState{Sum: sum, Target: target, Event: true}) log.Printf("depo-events: справочник участников записан в FTV_M2M_HANDBOOK: %d строк + событие HANDBOOK", n) return nil } @@ -1572,3 +1598,41 @@ func depoProcsMissing(ctx context.Context, db *sql.DB, f FancyMSSQLSettings) str } return "" } + +// depoHandbookState — что записано в DEPO в последний раз: отпечаток состава справочника, куда (сервер/база) +// и записано ли событие HANDBOOK. Без изменений справочник в DEPO не переписывается. +type depoHandbookState struct { + Sum string `json:"sum"` + Target string `json:"target"` + Event bool `json:"event"` +} + +const depoHandbookStateFile = "/var/lib/bj/.bj/depo-handbook-written.json" + +func depoHandbookStateLoad() depoHandbookState { + var st depoHandbookState + if b, err := os.ReadFile(depoHandbookStateFile); err == nil { + _ = json.Unmarshal(b, &st) + } + return st +} + +func depoHandbookStateSave(st depoHandbookState) { + if b, err := json.Marshal(st); err == nil { + _ = os.WriteFile(depoHandbookStateFile, b, 0o640) + } +} + +// depoHandbookSum — отпечаток состава справочника (участники и места расчётов, без даты справочника). +func depoHandbookSum(parts, places []HandbookEntry) string { + var lines []string + for _, e := range parts { + lines = append(lines, strings.Join([]string{"P", e.INN, e.Code, e.Kind, e.Name, e.FullName, e.DisplayName, e.EngName}, "|")) + } + for _, e := range places { + lines = append(lines, strings.Join([]string{"S", e.INN, e.Code, e.Name, e.FullName, e.DisplayName, e.EngName}, "|")) + } + sort.Strings(lines) + h := sha256.Sum256([]byte(strings.Join(lines, "\n"))) + return hex.EncodeToString(h[:12]) +}