From 6a6e58e88e85f5f18390d15244158529352fa376 Mon Sep 17 00:00:00 2001 From: Mikhail Chechnev Date: Thu, 9 Oct 2025 19:17:59 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A6=D0=98=D0=A2=D0=9A-979=20-=20=D0=94=D0=B8?= =?UTF-8?q?=D0=B0=D0=BB=D0=BE=D0=B3=20=D0=BD=D0=B0=D1=81=D1=82=D1=80=D0=BE?= =?UTF-8?q?=D0=B9=D0=BA=D0=B8=20=D0=B0=D1=82=D1=80=D0=B8=D0=B1=D1=83=D1=82?= =?UTF-8?q?=D0=B0,=20=D0=B2=D0=B2=D0=BE=D0=B4=20=D0=B8=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=BD=D1=82=D1=80=D0=BE=D0=BB=D1=8C=20=D0=BF=D1=81=D0=B5=D0=B2?= =?UTF-8?q?=D0=B4=D0=BE=D0=BD=D0=B8=D0=BC=D0=B0=20=D0=B0=D1=82=D1=80=D0=B8?= =?UTF-8?q?=D0=B1=D1=83=D1=82=D0=BE=D0=B2=20=D1=81=D1=83=D1=89=D0=BD=D0=BE?= =?UTF-8?q?=D1=81=D1=82=D0=B5=D0=B9,=20=D0=BA=D0=BE=D1=80=D0=BE=D1=82?= =?UTF-8?q?=D0=BA=D0=B8=D0=B5=20=D0=BF=D1=81=D0=B5=D0=B2=D0=B4=D0=BE=D0=BD?= =?UTF-8?q?=D0=B8=D0=BC=D1=8B=20=D1=81=D1=83=D1=89=D0=BD=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D0=B5=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.text.js | 3 +- .../inspector_query_ents/attr_setup_dialog.js | 73 ++++++ .../inspector_query_ents/attrs_list.js | 16 +- .../entity_attrs_dialog.js | 24 +- db/PKG_P8PANELS_QE.pck | 6 + db/PKG_P8PANELS_QE_BASE.pck | 213 +++++++++++++++--- 6 files changed, 297 insertions(+), 38 deletions(-) create mode 100644 app/panels/query_editor/components/inspector_query_ents/attr_setup_dialog.js diff --git a/app.text.js b/app.text.js index 333f1ff..fe03322 100644 --- a/app.text.js +++ b/app.text.js @@ -15,7 +15,8 @@ export const TITLES = { DEFAULT_PANELS_GROUP: "Без привязки к группе", //Заголовок группы панелей по умолчанию DATA_SOURCE_CONFIG: "Настройка источника данных", //Заголовок для настройки источника данных INSERT: "Добавление", //Заголовок для диалогов/форм добавления - UPDATE: "Исправление" //Заголовок для диалогов/форм исправления + UPDATE: "Исправление", //Заголовок для диалогов/форм исправления + CONFIG: "Настройка" //Заголовок для диалога настройки }; //Текст diff --git a/app/panels/query_editor/components/inspector_query_ents/attr_setup_dialog.js b/app/panels/query_editor/components/inspector_query_ents/attr_setup_dialog.js new file mode 100644 index 0000000..ca613d7 --- /dev/null +++ b/app/panels/query_editor/components/inspector_query_ents/attr_setup_dialog.js @@ -0,0 +1,73 @@ +/* + Парус 8 - Панели мониторинга - Редактор запросов + Компонент: Диалог настройки атрибута сущности +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента +import { P8PDialog } from "../../../../components/p8p_dialog"; //Типовой диалог +import { TITLES } from "../../../../../app.text"; //Общие текстовые ресурсы приложения +import { DATA_TYPE } from "../../common"; //Общие константы редактора +import { ATTRIBUTE_SHAPE } from "../attribute/attribute"; //Описание атрибута + +//----------- +//Тело модуля +//----------- + +//Диалог добавления/исправления аргумента запроса +const AttrSetupDialog = ({ attr, onOk, onCancel }) => { + //Достанем необходимое из атрибута + const { name, title, dataType, alias } = attr; + + //Нажатие на кнопку "Ok" + const handleOk = values => onOk && onOk({ ...values }); + + //Нажатие на кнопку "Отмена" + const handleCancel = () => onCancel && onCancel(); + + //Генерация содержимого + return ( + + ); +}; + +//Контроль свойств - Диалог настройки атрибута сущности +AttrSetupDialog.propTypes = { + attr: ATTRIBUTE_SHAPE.isRequired, + onOk: PropTypes.func, + onCancel: PropTypes.func +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { AttrSetupDialog }; diff --git a/app/panels/query_editor/components/inspector_query_ents/attrs_list.js b/app/panels/query_editor/components/inspector_query_ents/attrs_list.js index 72ddbfd..7942003 100644 --- a/app/panels/query_editor/components/inspector_query_ents/attrs_list.js +++ b/app/panels/query_editor/components/inspector_query_ents/attrs_list.js @@ -30,7 +30,7 @@ const STYLES = { //----------- //Список атрибутов сущности -const AttrsList = ({ attrs = [], onSelect = null, onShow = null } = {}) => { +const AttrsList = ({ attrs = [], onSelect = null, onShow = null, onSetup = null } = {}) => { //При выборе элемента списка const handleSelectClick = attr => { onSelect && onSelect(attr); @@ -42,6 +42,12 @@ const AttrsList = ({ attrs = [], onSelect = null, onShow = null } = {}) => { onShow && onShow(attr); }; + //При нажатии на кнопку настройки атрибута + const handleSetupClick = (e, attr) => { + e.stopPropagation(); + onSetup && onSetup(attr); + }; + //Формирование представления return ( @@ -60,7 +66,7 @@ const AttrsList = ({ attrs = [], onSelect = null, onShow = null } = {}) => { secondaryTypographyProps={{ component: "div" }} secondary={ - {`${attr.alias || attr.name}`} + {`${attr.name}${attr.alias ? ` (${attr.alias})` : ""}`} } /> @@ -68,6 +74,9 @@ const AttrsList = ({ attrs = [], onSelect = null, onShow = null } = {}) => { handleShowClick(e, attr)} title={showTitle}> {showIcon} + handleSetupClick(e, attr)} title={"Настроить"}> + settings + @@ -81,7 +90,8 @@ const AttrsList = ({ attrs = [], onSelect = null, onShow = null } = {}) => { AttrsList.propTypes = { attrs: PropTypes.arrayOf(ATTRIBUTE_SHAPE), onSelect: PropTypes.func, - onShow: PropTypes.func + onShow: PropTypes.func, + onSetup: PropTypes.func }; //---------------- diff --git a/app/panels/query_editor/components/inspector_query_ents/entity_attrs_dialog.js b/app/panels/query_editor/components/inspector_query_ents/entity_attrs_dialog.js index 11c0d14..a112047 100644 --- a/app/panels/query_editor/components/inspector_query_ents/entity_attrs_dialog.js +++ b/app/panels/query_editor/components/inspector_query_ents/entity_attrs_dialog.js @@ -13,6 +13,7 @@ import { Box, TextField, InputAdornment, Icon, IconButton } from "@mui/material" import { P8PDialog, P8P_DIALOG_WIDTH } from "../../../../components/p8p_dialog"; //Типовой диалог import { P8PComponentInlineMessage } from "../../../../components/editors/p8p_component_inline_message"; //Типовое встраиваемое сообщение import { AttrsList } from "./attrs_list"; //Список атрибутов сущности +import { AttrSetupDialog } from "./attr_setup_dialog"; //Диалог настройки атрибута import { useEntityAttrs } from "./hooks"; //Хуки диалога настройки атрибутов сущности //--------- @@ -36,6 +37,9 @@ const EntityAttrsDialog = ({ query, id, title, onOk, onCancel }) => { //Собственное состояние - список атрибутов const [attrs, setAttrs] = useState([]); + //Собственное состояние - настраиваемый атрибут + const [setupAttr, setSetupAttr] = useState(null); + //Хук для взаимодействия с сервером const [srvAttrs, saveAttrs] = useEntityAttrs(query, id); @@ -64,6 +68,21 @@ const EntityAttrsDialog = ({ query, id, title, onOk, onCancel }) => { })) ); + //Настройка атрибута + const handleAttrSetup = attr => setSetupAttr({ ...attr }); + + //При закрытии диалога настройки атрибута по "ОК" + const handleAttrSetupOk = values => { + setAttrs( + attrs.map(a => ({ + ...(a.id === setupAttr.id ? { ...a, use: 1, alias: values.alias } : a) + })) + ); + setSetupAttr(null); + }; + + const handleAttrSetupCancel = () => setSetupAttr(null); + //При изменении значения фильтра const handleFilterChange = e => setFilter(e.target.value); @@ -87,6 +106,7 @@ const EntityAttrsDialog = ({ query, id, title, onOk, onCancel }) => { //Генерация содержимого return ( + {setupAttr && } { }} /> - {displayAttrsList === true && } + {displayAttrsList === true && ( + + )} {displayAttrsList === false && ( RENTS(NENT_INDEX).RATTRS, SCHECK_MSG => SCHECK_MSG); + if (SCHECK_MSG is not null) then + P_EXCEPTION(0, SCHECK_MSG); + end if; /* Сохраняем обновленный набор сущностей */ PKG_P8PANELS_QE_BASE.QUERY_ENTS_SET(NRN => RQ.RN, RENTS => RENTS); /* Сохраняем обновленный набор связей */ diff --git a/db/PKG_P8PANELS_QE_BASE.pck b/db/PKG_P8PANELS_QE_BASE.pck index 42d1a69..56ba0f4 100644 --- a/db/PKG_P8PANELS_QE_BASE.pck +++ b/db/PKG_P8PANELS_QE_BASE.pck @@ -69,6 +69,13 @@ create or replace package PKG_P8PANELS_QE_BASE as SVIEW_NAME in varchar2 -- Имя представления ) return varchar2; -- Заголовок представления из метаданных + /* Поиск индекса атрибута по псевдониму */ + function TATTRS_INDEX_BY_ALIAS + ( + RATTRS in TATTRS, -- Коллекция атрибутов + SALIAS in varchar2 -- Искомый псевдоним + ) return number; -- Индекс найденного атрибута (null - если не найдено) + /* Десериализация коллекции атрибутов сущности (из BASE64) */ function TATTRS_FROM_XML_BASE64 ( @@ -77,6 +84,13 @@ create or replace package PKG_P8PANELS_QE_BASE as BADD_ROOT in boolean := false -- Флаг необходимости добавления корневого тэга (false - нет, true - да) ) return TATTRS; -- Коллекция атрибутов сущности + /* Проверка корректности атрибутов сущности */ + procedure TATTRS_CHECK + ( + RATTRS in TATTRS, -- Коллекция атрибутов сущности + SCHECK_MSG out varchar2 -- Сообщения проверки (null - ошибок нет) + ); + /* Поиск индекса аргумента по имени */ function TARGS_INDEX_BY_NAME ( @@ -809,6 +823,36 @@ create or replace package body PKG_P8PANELS_QE_BASE as return SENT_ID || '.' || SNAME; end TATTR_ID_MAKE; + /* Формирование псевдонима атрибута сущности */ + function TATTR_ALIAS_MAKE + ( + SENT_ID in varchar2, -- Уникальный идентификатор родительской сущности + RATTRS in TATTRS, -- Коллекция уже существующих атрибутов + NINDEX in number -- Индекс элемента, для которого формируется псевдоним + ) return varchar2 -- Сформированный идентификатор + is + SRES PKG_STD.TSTRING; -- Буфер для результата + NCNT PKG_STD.TNUMBER := 0; -- Текущая попытка + NMAX_CNT PKG_STD.TNUMBER := 1000; -- Предельное количество попыток + NATTR_INDEX PKG_STD.TNUMBER := 0; -- Индекс поля в коллекции атрибутов сущности, при проверке уникальности псевдонима + begin + /* Подбираем псевдоним */ + while ((NATTR_INDEX is not null) and (NCNT < NMAX_CNT)) + loop + SRES := SENT_ID || TO_CHAR(NINDEX + NCNT); + NATTR_INDEX := TATTRS_INDEX_BY_ALIAS(RATTRS => RATTRS, SALIAS => SRES); + NCNT := NCNT + 1; + end loop; + /* Если так и не смогли сформировать уникальный псевдоним */ + if ((NATTR_INDEX is not null) and (NCNT >= NMAX_CNT)) then + P_EXCEPTION(0, + 'Не удалось сформировать уникальный псевдоним для атрибута сущности "%s".', + SENT_ID); + end if; + /* Вернём полученный псевдоним */ + return SRES; + end TATTR_ALIAS_MAKE; + /* Сериализация атрибута сущности */ procedure TATTR_TO_XML ( @@ -929,6 +973,61 @@ create or replace package body PKG_P8PANELS_QE_BASE as /* Ничего не нашли */ return null; end TATTRS_INDEX_BY_NAME; + + /* Поиск индекса атрибута по псевдониму */ + function TATTRS_INDEX_BY_ALIAS + ( + RATTRS in TATTRS, -- Коллекция атрибутов + SALIAS in varchar2 -- Искомый псевдоним + ) return number -- Индекс найденного атрибута (null - если не найдено) + is + begin + /* Обходим коллекцию */ + if ((RATTRS is not null) and (RATTRS.COUNT > 0)) then + for I in RATTRS.FIRST .. RATTRS.LAST + loop + begin + /* Возвращаем найденный индекс */ + if (RATTRS(I).SALIAS = SALIAS) then + return I; + end if; + exception + when NO_DATA_FOUND then + null; + end; + end loop; + end if; + /* Ничего не нашли */ + return null; + end TATTRS_INDEX_BY_ALIAS; + + /* Подсчет количества атрибутов по псевдониму */ + function TATTRS_COUNT_BY_ALIAS + ( + RATTRS in TATTRS, -- Коллекция атрибутов + SALIAS in varchar2 -- Искомый псевдоним + ) return number -- Количество найденных атрибутов + is + NRES PKG_STD.TNUMBER := 0; --Буфер для расчета + begin + /* Обходим коллекцию */ + if ((RATTRS is not null) and (RATTRS.COUNT > 0)) then + for I in RATTRS.FIRST .. RATTRS.LAST + loop + begin + /* Увеличиваем счетчик, если наш клиент */ + if (RATTRS(I).SALIAS = SALIAS) then + NRES := NRES + 1; + end if; + exception + when NO_DATA_FOUND then + null; + end; + end loop; + end if; + /* Вернем количество найденных */ + return NRES; + end TATTRS_COUNT_BY_ALIAS; /* Формирование списка атрибутов сущности */ function TATTRS_MAKE @@ -995,6 +1094,7 @@ create or replace package body PKG_P8PANELS_QE_BASE as RRES(RRES.LAST).STITLE := DMSCLVIEWSATTRS_TITLE_GET(SVIEW_NAME => RENT.SNAME, SATTR_NAME => RRES(RRES.LAST).SNAME); RRES(RRES.LAST).NDATA_TYPE := RVIEW_FIELD.DATA_TYPE; + RRES(RRES.LAST).SALIAS := TATTR_ALIAS_MAKE(SENT_ID => RENT.SID, RATTRS => RRES, NINDEX => RRES.LAST); /* Если это инициализиция сущности - установим флаг применения в запросе (тогда атрибут будет отображен в диаграмме) */ if (BINIT) then RRES(RRES.LAST).NUSE := 1; @@ -1108,24 +1208,61 @@ create or replace package body PKG_P8PANELS_QE_BASE as return TATTRS_FROM_XML(CXML => CTMP); end TATTRS_FROM_XML_BASE64; + /* Проверка корректности атрибутов сущности */ + procedure TATTRS_CHECK + ( + RATTRS in TATTRS, -- Коллекция атрибутов сущности + SCHECK_MSG out varchar2 -- Сообщения проверки (null - ошибок нет) + ) + is + begin + /* Обходим коллекцию */ + if ((RATTRS is not null) and (RATTRS.COUNT > 0)) then + for I in RATTRS.FIRST .. RATTRS.LAST + loop + /* Псевдоним должен быть задан */ + if (RATTRS(I).SALIAS is null) then + SCHECK_MSG := SCHECK_MSG || 'Для атрибута "' || RATTRS(I).SNAME || '" не задан псевдоним; '; + else + /* А если задан - должен соответствовать по длине */ + if (LENGTH(RATTRS(I).SALIAS) > 30) then + SCHECK_MSG := SCHECK_MSG || 'Длина псевдонима атрибута "' || RATTRS(I).SNAME || + '" превышает максимально допустимую (30 символов); '; + end if; + /* Набору символов */ + if (INSTR(RATTRS(I).SALIAS, '"') <> 0) then + SCHECK_MSG := SCHECK_MSG || 'Псевдоним атрибута "' || RATTRS(I).SNAME || + '" содержит недопустимые символы; '; + end if; + /* Быть уникальным */ + if (TATTRS_COUNT_BY_ALIAS(RATTRS => RATTRS, SALIAS => RATTRS(I).SALIAS) > 1) then + SCHECK_MSG := SCHECK_MSG || 'Нарушение уникальности псевдонима атрибута "' || RATTRS(I).SNAME || '"; '; + end if; + end if; + end loop; + end if; + if (SCHECK_MSG is not null) then + SCHECK_MSG := RTRIM(SCHECK_MSG, '; '); + end if; + end TATTRS_CHECK; + /* Формирование идентификатора сущности */ function TENT_ID_MAKE ( - SNAME in varchar2, -- Имя сущности + STYPE in varchar2, -- Тип (см. константы SENT_TYPE_*) NNUMB in number -- Номер сущности в запросе ) return varchar2 -- Сформированный идентификатор is begin /* Проверим параметры */ - if (SNAME is null) then - P_EXCEPTION(0, 'Не указано имя сущности.'); + if (STYPE is null) then + P_EXCEPTION(0, 'Не указан тип сущности.'); + end if; + if (NNUMB is null) then + P_EXCEPTION(0, 'Не указан номер сущности в запросе.'); end if; /* Соберем идентификатор */ - if (NNUMB is null) then - return SNAME; - else - return SNAME || TO_CHAR(NNUMB); - end if; + return SUBSTR(STYPE, 1, 1) || TO_CHAR(NNUMB); end TENT_ID_MAKE; /* Формирование описания сущности */ @@ -1154,7 +1291,7 @@ create or replace package body PKG_P8PANELS_QE_BASE as /* Получим описание представления */ RVIEW := PKG_OBJECT_DESC.DESC_VIEW(SVIEW_NAME => SNAME, BRAISE_ERROR => true); /* Собираем заголовок сущности */ - RENT.SID := TENT_ID_MAKE(SNAME => RVIEW.VIEW_NAME, NNUMB => NNUMB); + RENT.SID := TENT_ID_MAKE(STYPE => STYPE, NNUMB => NNUMB); RENT.SNAME := RVIEW.VIEW_NAME; RENT.STITLE := DMSCLVIEWS_TITLE_GET(SVIEW_NAME => RENT.SNAME); RENT.STYPE := SENT_TYPE_VIEW; @@ -1306,13 +1443,13 @@ create or replace package body PKG_P8PANELS_QE_BASE as function TENTS_NEXT_NUMB ( RENTS in TENTS, -- Коллекция сущностей - SNAME in varchar2 -- Имя + STYPE in varchar2 -- Тип (см. константы SENT_TYPE_*) ) return number -- Номер сущности в коллекции is NNUMB PKG_STD.TNUMBER := 0; -- Буфер для результата begin /* Подбираем первый свободный номер */ - while (TENTS_INDEX_BY_ID(RENTS => RENTS, SID => TENT_ID_MAKE(SNAME => SNAME, NNUMB => NNUMB)) is not null) + while (TENTS_INDEX_BY_ID(RENTS => RENTS, SID => TENT_ID_MAKE(STYPE => STYPE, NNUMB => NNUMB)) is not null) loop NNUMB := NNUMB + 1; end loop; @@ -1338,7 +1475,7 @@ create or replace package body PKG_P8PANELS_QE_BASE as /* Формируем пописание сущности */ RENT := TENT_MAKE(SNAME => SNAME, STYPE => STYPE, - NNUMB => TENTS_NEXT_NUMB(RENTS => RENTS, SNAME => SNAME), + NNUMB => TENTS_NEXT_NUMB(RENTS => RENTS, STYPE => STYPE), NINIT_ATTRS => NINIT_ATTRS); /* Добавляем её в коллекцию */ RENTS.EXTEND(); @@ -2580,6 +2717,7 @@ create or replace package body PKG_P8PANELS_QE_BASE as /* Если сущность одна - то она и главная */ if (RENTS.COUNT = 1) then RENT := RENTS(RENTS.FIRST); + return; end if; /* Обходим все сущности */ for E in RENTS.FIRST .. RENTS.LAST @@ -2590,13 +2728,15 @@ create or replace package body PKG_P8PANELS_QE_BASE as for A in RENTS(E).RATTRS.FIRST .. RENTS(E).RATTRS.LAST loop /* Обойдем связи и поищем атрибут среди источников */ - for R in RRLS.FIRST .. RRLS.LAST - loop - if (RRLS(R).SSOURCE = RENTS(E).RATTRS(A).SID) then - BFOUND := true; - exit; - end if; - end loop; + if ((RRLS is not null) and (RRLS.COUNT > 0)) then + for R in RRLS.FIRST .. RRLS.LAST + loop + if (RRLS(R).SSOURCE = RENTS(E).RATTRS(A).SID) then + BFOUND := true; + exit; + end if; + end loop; + end if; /* Если хоть один атрибут сущности есть среди источников - дальше можно атрибуты этой сущности не рассматривать */ exit when BFOUND = true; end loop; @@ -2621,12 +2761,14 @@ create or replace package body PKG_P8PANELS_QE_BASE as /* Формирование части "select" */ procedure BUILD_SELECT ( - RENTS TENTS, -- Сущности запроса - SQRY in out varchar2 -- Буфер для запроса + RENTS TENTS, -- Сущности запроса + SQRY in out varchar2 -- Буфер для запроса ) is - SFIELD PKG_STD.TSTRING; -- Буфер для поля запроса + SFIELD PKG_STD.TSTRING; -- Буфер для поля запроса + BFOUND boolean := false; -- Флаг наличия визуализируемых атрибутов begin + /* Формируем */ PKG_SQL_BUILD.APPEND(SSQL => SQRY, SELEMENT1 => 'select '); for I in RENTS.FIRST .. RENTS.LAST loop @@ -2634,15 +2776,24 @@ create or replace package body PKG_P8PANELS_QE_BASE as for J in RENTS(I).RATTRS.FIRST .. RENTS(I).RATTRS.LAST loop if (RENTS(I).RATTRS(J).NSHOW = 1) then - SFIELD := RENTS(I).RATTRS(J).SID || ' ' || RENTS(I).RATTRS(J).SNAME || I || J; - if (not ((I = RENTS.LAST) and (J = RENTS(I).RATTRS.LAST))) then - SFIELD := SFIELD || ', '; + if (RENTS(I).RATTRS(J).SALIAS is null) then + P_EXCEPTION(0, + 'Для атрибута ("%s") не задан севдоним.', + RENTS(I).RATTRS(J).SID); end if; + SFIELD := RENTS(I).RATTRS(J).SID || ' "' || RENTS(I).RATTRS(J).SALIAS || '", '; PKG_SQL_BUILD.APPEND(SSQL => SQRY, SELEMENT1 => SFIELD); + BFOUND := true; end if; end loop; end if; end loop; + SQRY := RTRIM(SQRY, ', '); + /* Если не нашли ни одного атрибута */ + if (not BFOUND) then + P_EXCEPTION(0, + 'Для запроса не определено ни одного визуализируемого атрибута.'); + end if; end BUILD_SELECT; /* Формирование иерархии связей в секции "from" */ @@ -2701,7 +2852,7 @@ create or replace package body PKG_P8PANELS_QE_BASE as PKG_SQL_BUILD.APPEND(SSQL => SQRY, SELEMENT1 => 'from '); STABLE := RENT_ROOT.SNAME || ' ' || RENT_ROOT.SID; PKG_SQL_BUILD.APPEND(SSQL => SQRY, SELEMENT1 => STABLE); - if (RRLS.COUNT > 0) then + if ((RRLS is not null) and (RRLS.COUNT > 0)) then BUILD_FROM_HIER(RENT_START => RENT_ROOT, RENTS => RENTS, RRLS => RRLS, SQRY => SQRY); end if; else @@ -2723,10 +2874,6 @@ create or replace package body PKG_P8PANELS_QE_BASE as end if; end BUILD_WHERE; begin - /* - TODO: owner="root" created="09.09.2025" - text="Контроль длины псевдонима таблицы и поля (максимум 30 символов)" - */ /* TODO: owner="root" created="10.09.2025" text="Предусмотреть связям признак ""обязательность"" @@ -2746,14 +2893,14 @@ create or replace package body PKG_P8PANELS_QE_BASE as /* Читаем сущности запроса */ RENTS := QUERY_ENTS_GET(CENTS => RQ.ENTS); /* Нет сущностей - нет запроса */ - if ((RENTS is null) and (RENTS.COUNT = 0)) then + if ((RENTS is null) or (RENTS.COUNT = 0)) then SET_MSG(SMESSAGE => 'В запросе нет сущностей.'); return; end if; /* Читаем связи запроса */ RRLS := QUERY_RLS_GET(CRLS => RQ.RLS); /* Нельзя построить запрос, если есть несвязанные сущности */ - if ((RENTS.COUNT > 1) and (RENTS.COUNT - 1 > RRLS.COUNT)) then + if ((RENTS.COUNT > 1) and ((RRLS is null) or (RENTS.COUNT - 1 > RRLS.COUNT))) then SET_MSG(SMESSAGE => 'В запросе есть несвязанные сущности.'); return; end if; @@ -2766,7 +2913,7 @@ create or replace package body PKG_P8PANELS_QE_BASE as /* Собираем запрос - таблицы и связи */ BUILD_FROM(RENTS => RENTS, RRLS => RRLS, SQRY => SQRY); /* Собираем запрос - условия */ - BUILD_WHERE(ROPT => ROPT, SQRY => SQRY); + BUILD_WHERE(ROPT => ROPT, SQRY => SQRY); exception when others then PKG_STATE.DIAGNOSTICS_STACKED();