ЦИТК-979 - Диалог настройки атрибута, ввод и контроль псевдонима атрибутов сущностей, короткие псевдонимы сущностей

This commit is contained in:
Mikhail Chechnev 2025-10-09 19:17:59 +03:00
parent 0767a12fa6
commit 6a6e58e88e
6 changed files with 297 additions and 38 deletions

View File

@ -15,7 +15,8 @@ export const TITLES = {
DEFAULT_PANELS_GROUP: "Без привязки к группе", //Заголовок группы панелей по умолчанию
DATA_SOURCE_CONFIG: "Настройка источника данных", //Заголовок для настройки источника данных
INSERT: "Добавление", //Заголовок для диалогов/форм добавления
UPDATE: "Исправление" //Заголовок для диалогов/форм исправления
UPDATE: "Исправление", //Заголовок для диалогов/форм исправления
CONFIG: "Настройка" //Заголовок для диалога настройки
};
//Текст

View File

@ -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 (
<P8PDialog
title={`${TITLES.CONFIG} атрибута "${title}"`}
inputs={[
{ name: "name", value: name, label: "Имя", disabled: true },
{ name: "title", value: title, label: "Наименование", disabled: true },
{
name: "dataType",
value: dataType,
label: "Тип данных",
list: [
{ name: "Строка", value: DATA_TYPE.STR },
{ name: "Число", value: DATA_TYPE.NUMB },
{ name: "Дата", value: DATA_TYPE.DATE }
],
disabled: true
},
{
name: "alias",
value: alias,
label: "Псевдоним"
}
]}
onOk={handleOk}
onCancel={handleCancel}
/>
);
};
//Контроль свойств - Диалог настройки атрибута сущности
AttrSetupDialog.propTypes = {
attr: ATTRIBUTE_SHAPE.isRequired,
onOk: PropTypes.func,
onCancel: PropTypes.func
};
//----------------
//Интерфейс модуля
//----------------
export { AttrSetupDialog };

View File

@ -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 (
<List sx={STYLES.LIST}>
@ -60,7 +66,7 @@ const AttrsList = ({ attrs = [], onSelect = null, onShow = null } = {}) => {
secondaryTypographyProps={{ component: "div" }}
secondary={
<Stack direction={"column"}>
<Typography variant={"caption"}>{`${attr.alias || attr.name}`}</Typography>
<Typography variant={"caption"}>{`${attr.name}${attr.alias ? ` (${attr.alias})` : ""}`}</Typography>
</Stack>
}
/>
@ -68,6 +74,9 @@ const AttrsList = ({ attrs = [], onSelect = null, onShow = null } = {}) => {
<IconButton onClick={e => handleShowClick(e, attr)} title={showTitle}>
<Icon>{showIcon}</Icon>
</IconButton>
<IconButton onClick={e => handleSetupClick(e, attr)} title={"Настроить"}>
<Icon>settings</Icon>
</IconButton>
</Stack>
</ListItemButton>
</ListItem>
@ -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
};
//----------------

View File

@ -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 (
<P8PDialog title={`Атрибуты сущности "${title}"`} width={P8P_DIALOG_WIDTH.SM} fullWidth onOk={handleOk} onCancel={handleCancel}>
{setupAttr && <AttrSetupDialog attr={setupAttr} onOk={handleAttrSetupOk} onCancel={handleAttrSetupCancel} />}
<TextField
margin={"normal"}
variant={"standard"}
@ -110,7 +130,9 @@ const EntityAttrsDialog = ({ query, id, title, onOk, onCancel }) => {
}}
/>
<Box sx={STYLES.LIST_CONTAINER} justifyContent={"center"} alignItems={"center"} display={"flex"}>
{displayAttrsList === true && <AttrsList attrs={filteredAttrs} filter={filter} onSelect={handleAttrSelect} onShow={handleAttrShow} />}
{displayAttrsList === true && (
<AttrsList attrs={filteredAttrs} filter={filter} onSelect={handleAttrSelect} onShow={handleAttrShow} onSetup={handleAttrSetup} />
)}
{displayAttrsList === false && (
<P8PComponentInlineMessage
name={"Нет данных, соответствующих фильтру"}

View File

@ -395,6 +395,7 @@ create or replace package body PKG_P8PANELS_QE as
NENT_INDEX PKG_STD.TNUMBER; -- Индекс изменяемой сущности
RATTRS PKG_P8PANELS_QE_BASE.TATTRS; -- Коллекция полученных атриубтов
RRLS PKG_P8PANELS_QE_BASE.TRLS; -- Коллекция существующих связей
SCHECK_MSG PKG_STD.TLSTRING; -- Сообщение при проверке атрибутов
SQRY PKG_STD.TLSTRING; -- SQL-выражение запроса
SQRY_MSG PKG_STD.TLSTRING; -- Сообщение при формировании SQL-выражения
begin
@ -434,6 +435,11 @@ create or replace package body PKG_P8PANELS_QE as
end if;
end loop;
end if;
/* Проверим, что новый набор атрибутов сущности корректен */
PKG_P8PANELS_QE_BASE.TATTRS_CHECK(RATTRS => 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);
/* Сохраняем обновленный набор связей */

View File

@ -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();