1С: проверка поручений МНП перед отправкой в НРД

Часть модуля onec-back, по умолчанию выключена, включается в настройках
(«Проверка поручений в 1С»). Исходящий перевод после блокировки бумаг в
МНП ждёт решения 1С и в НРД не уходит, пока 1С не подтвердит каждое
поручение; при отказе bj снимает поручения в QUIK с причиной из 1С и
отклоняет перевод в журнале и кабинете клиента.

- onec.instr / onec.instr_queue / onec.set_decision в базе bj: 1С читает
  очередь и пишет решение под логином onec_bj (только эти права); чтение
  очереди отмечает связь с 1С.
- bj-onec-admin (root, через bj-keymedia): логин onec_bj, вход только с
  IP 1С по TLS (pg_hba.conf), listen_addresses, ufw; revoke закрывает вход.
- onecLoop: отправка подтверждённых, отказы, снятие клиентом в QUIK,
  поручения из терминала, сроки решения и потеря связи — оповещения.
- Карточка заявки: блок «Проверка 1С», решение оператора вместо 1С.

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-22 20:13:59 +03:00
parent e51962c5bb
commit 21d5f33394
13 changed files with 1598 additions and 5 deletions
+2
View File
@@ -17,6 +17,8 @@ process_one(){
rng-status|rng-start|rng-cancel) out=$(/usr/local/sbin/bj-validata-rng "${action#rng-}" 2>&1) ;;
# системные настройки сервера: серверы времени (NTP)
system-ntp|system-tz|system-sync|system-ports) out=$(BJ_EXTRA="$extra" /usr/local/sbin/bj-system-admin "${action#system-}" 2>&1) ;;
# вход 1С в базу bj (проверка поручений в 1С): логин onec_bj, pg_hba.conf; параметры — JSON в extra
onec-access|onec-revoke) out=$(BJ_EXTRA="$extra" /usr/local/sbin/bj-onec-admin "${action#onec-}" 2>&1) ;;
# резервное копирование и восстановление (настройки bj → bj-backup-admin); параметры — JSON в extra
backup-config|backup-test|backup-run|backup-list|backup-verify|backup-restore) out=$(BJ_EXTRA="$extra" /usr/local/sbin/bj-backup-admin "${action#backup-}" 2>&1) ;;
# Перезапуск службы модуля из админки. Список закрытый: чужую службу
+104
View File
@@ -0,0 +1,104 @@
#!/bin/bash
# bj-onec-admin — вход 1С в базу PostgreSQL bj для проверки поручений в 1С (через root-демон bj-keymedia).
# access — логин onec_bj с паролем, вход в базу bj только с IP сервера 1С и только по TLS:
# свои строки в начале pg_hba.conf (# bj-onec), PostgreSQL слушает адрес сервера bj,
# с которого виден сервер 1С (перезапуск PostgreSQL — только если этот адрес не слушался),
# порт 5432 в ufw для IP 1С, если ufw включён. BJ_EXTRA: {"password":"…","ips":"10.0.0.5/32,…"}
# revoke — закрыть вход: NOLOGIN, строки # bj-onec из pg_hba.conf и правила ufw убрать
# Права на объекты onec.* выдаёт сам bj (он их владелец).
set -uo pipefail
q() { python3 -c "import json,sys;print(json.dumps(sys.argv[1],ensure_ascii=False))" "$1"; }
ok() { echo "{\"ok\":true,\"output\":$(q "$1")${2:-}}"; exit 0; }
err() { echo "{\"ok\":false,\"error\":$(q "$1")}"; exit 0; }
x() { python3 -c "import json,os,sys;print(json.loads(os.environ.get('BJ_EXTRA') or '{}').get(sys.argv[1],''))" "$1" 2>/dev/null; }
cd /
psqlq() { runuser -u postgres -- psql -X -q -At -v ON_ERROR_STOP=1 "$@"; }
LOGIN=onec_bj
DB=bj
MARK='# bj-onec'
hba=$(psqlq -d postgres -c 'show hba_file' 2>&1) || err "PostgreSQL недоступен: $hba"
[ -f "$hba" ] || err "не найден pg_hba.conf ($hba)"
# hba_set "строки" — заменить наш блок (пусто — убрать) в начале pg_hba.conf
hba_set() {
[ -f "$hba.bak-bj-onec" ] || cp -a "$hba" "$hba.bak-bj-onec"
python3 - "$hba" "$MARK" "$1" <<'PY' || return 1
import sys
p, mark, new = sys.argv[1], sys.argv[2], sys.argv[3]
old = [l for l in open(p).read().split("\n") if not l.rstrip().endswith(mark)]
add = [l for l in new.split("\n") if l.strip()]
open(p, "w").write("\n".join(add + old).rstrip("\n") + "\n")
PY
}
case "${1:-}" in
access)
pw=$(x password); ips=$(x ips)
[[ "$pw" =~ ^[A-Za-z0-9]{20,64}$ ]] || err "пароль: 20–64 латинских букв и цифр"
list=()
for ip in ${ips//,/ }; do
[[ "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}/(2[4-9]|3[0-2])$ ]] || err "недопустимый адрес 1С: $ip"
list+=("$ip")
done
[ ${#list[@]} -gt 0 ] || err "не указан IP-адрес сервера 1С"
[ "$(psqlq -d postgres -c 'show ssl')" = on ] || err "в PostgreSQL выключено шифрование (ssl = off) — 1С подключается только по TLS"
psqlq -d postgres -c "select 1 from pg_database where datname='$DB'" | grep -q 1 || err "нет базы $DB"
# логин: только вход, без прав на создание чего-либо; пароль — через переменную psql (не в командной строке)
printf '%s\n' \
"DO \$\$BEGIN IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '$LOGIN') THEN CREATE ROLE $LOGIN; END IF; END\$\$;" \
"ALTER ROLE $LOGIN WITH LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOBYPASSRLS CONNECTION LIMIT 5 PASSWORD :'pw';" \
"ALTER ROLE $LOGIN SET statement_timeout = '30s';" \
"ALTER ROLE $LOGIN SET search_path = onec;" \
| psqlq -d postgres -v pw="$pw" >/dev/null 2>&1 || err "логин $LOGIN не заведён"
# в схеме public базы bj создавать объекты может только bj (у PUBLIC право снимаем)
psqlq -d "$DB" -c "GRANT USAGE, CREATE ON SCHEMA public TO bj" -c "REVOKE CREATE ON SCHEMA public FROM PUBLIC" >/dev/null 2>&1 \
|| err "права на схему public в базе $DB не изменены"
lines=""
for ip in "${list[@]}"; do lines+="hostssl $DB $LOGIN $ip md5 $MARK"$'\n'; done
lines+="host all $LOGIN 0.0.0.0/0 reject $MARK"$'\n'"host all $LOGIN ::/0 reject $MARK"
hba_set "$lines" || err "не удалось записать $hba"
# адрес сервера bj, с которого виден сервер 1С
first=${list[0]%/*}
src=$(ip -o route get "$first" 2>/dev/null | sed -n 's/.* src \([0-9.]*\).*/\1/p' | head -1)
[ -n "$src" ] || err "нет маршрута до сервера 1С ($first)"
cur=$(psqlq -d postgres -c 'show listen_addresses')
restart=no
case ",${cur// /}," in
*,\*,*|*,"$src",*|*,0.0.0.0,*) ;;
*) new="${cur// /},$src"
printf '%s\n' "ALTER SYSTEM SET listen_addresses = :'v';" | psqlq -d postgres -v v="$new" >/dev/null 2>&1 || err "listen_addresses не изменён"
restart=yes ;;
esac
if [ "$restart" = yes ]; then
systemctl restart postgresql >/dev/null 2>&1 || err "PostgreSQL не перезапустился (listen_addresses = $new)"
for i in $(seq 1 30); do psqlq -d postgres -c 'select 1' >/dev/null 2>&1 && break; sleep 1; done
else
psqlq -d postgres -c 'select pg_reload_conf()' >/dev/null 2>&1 || err "PostgreSQL не перечитал pg_hba.conf"
fi
fw=""
if command -v ufw >/dev/null 2>&1 && ufw status 2>/dev/null | grep -q '^Status: active'; then
for ip in "${list[@]}"; do ufw allow from "$ip" to any port 5432 proto tcp comment bj-onec >/dev/null 2>&1; done
fw="; порт 5432 открыт в ufw"
fi
port=$(psqlq -d postgres -c 'show port')
note="логин $LOGIN, вход с ${list[*]} по TLS, сервер $src:$port"
[ "$restart" = yes ] && note+="; PostgreSQL теперь слушает $src (перезапущен)"
ok "$note$fw" ",\"host\":$(q "$src"),\"port\":$(q "$port")"
;;
revoke)
psqlq -d postgres -c "DO \$\$BEGIN IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '$LOGIN') THEN ALTER ROLE $LOGIN NOLOGIN; END IF; END\$\$;" >/dev/null 2>&1 \
|| err "вход логином $LOGIN не закрыт"
# открытые сеансы 1С — завершить
psqlq -d postgres -c "select pg_terminate_backend(pid) from pg_stat_activity where usename = '$LOGIN'" >/dev/null 2>&1
hba_set "" || err "не удалось записать $hba"
psqlq -d postgres -c 'select pg_reload_conf()' >/dev/null 2>&1
if command -v ufw >/dev/null 2>&1 && ufw status 2>/dev/null | grep -q '^Status: active'; then
while n=$(ufw status numbered 2>/dev/null | grep 'bj-onec' | head -1 | sed -n 's/^\[ *\([0-9]*\)\].*/\1/p') && [ -n "$n" ]; do
ufw --force delete "$n" >/dev/null 2>&1 || break
done
fi
ok "вход логином $LOGIN закрыт"
;;
*) err "команда: access | revoke" ;;
esac