From c6d21c83b5f00d77786676164db78c1934164023 Mon Sep 17 00:00:00 2001 From: Dollerino Date: Fri, 11 Jul 2025 18:00:29 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A6=D0=98=D0=A2=D0=9A-878=20-=20=D0=94=D0=BE?= =?UTF-8?q?=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BF=D0=B0?= =?UTF-8?q?=D0=BD=D0=B5=D0=BB=D0=B8=20"=D0=94=D0=BE=D1=81=D0=BA=D0=B8=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=B4=D0=B0=D1=87"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/panels/clnt_task_board/clnt_task_board.js | 316 ++ .../components/custom_input_field.js | 174 ++ .../components/filter_dialog.js | 336 ++ .../clnt_task_board/components/note_dialog.js | 99 + .../components/settings_dialog.js | 131 + .../clnt_task_board/components/status_card.js | 182 ++ .../components/status_card_settings.js | 109 + .../clnt_task_board/components/task_card.js | 376 +++ .../clnt_task_board/components/task_form.js | 158 + .../components/task_form_tab_executor.js | 155 + .../components/task_form_tab_info.js | 192 ++ .../components/task_form_tab_props.js | 169 + app/panels/clnt_task_board/filter.js | 212 ++ .../clnt_task_board/hooks/dict_hooks.js | 255 ++ .../clnt_task_board/hooks/filter_hooks.js | 122 + app/panels/clnt_task_board/hooks/hooks.js | 243 ++ .../hooks/task_dialog_hooks.js | 191 ++ .../clnt_task_board/hooks/tasks_hooks.js | 431 +++ app/panels/clnt_task_board/index.js | 16 + app/panels/clnt_task_board/layouts.js | 294 ++ app/panels/clnt_task_board/styles.js | 48 + app/panels/clnt_task_board/task_dialog.js | 180 ++ db/PKG_P8PANELS_CLNTTSKBRD.pck | 2705 +++++++++++++++++ 23 files changed, 7094 insertions(+) create mode 100644 app/panels/clnt_task_board/clnt_task_board.js create mode 100644 app/panels/clnt_task_board/components/custom_input_field.js create mode 100644 app/panels/clnt_task_board/components/filter_dialog.js create mode 100644 app/panels/clnt_task_board/components/note_dialog.js create mode 100644 app/panels/clnt_task_board/components/settings_dialog.js create mode 100644 app/panels/clnt_task_board/components/status_card.js create mode 100644 app/panels/clnt_task_board/components/status_card_settings.js create mode 100644 app/panels/clnt_task_board/components/task_card.js create mode 100644 app/panels/clnt_task_board/components/task_form.js create mode 100644 app/panels/clnt_task_board/components/task_form_tab_executor.js create mode 100644 app/panels/clnt_task_board/components/task_form_tab_info.js create mode 100644 app/panels/clnt_task_board/components/task_form_tab_props.js create mode 100644 app/panels/clnt_task_board/filter.js create mode 100644 app/panels/clnt_task_board/hooks/dict_hooks.js create mode 100644 app/panels/clnt_task_board/hooks/filter_hooks.js create mode 100644 app/panels/clnt_task_board/hooks/hooks.js create mode 100644 app/panels/clnt_task_board/hooks/task_dialog_hooks.js create mode 100644 app/panels/clnt_task_board/hooks/tasks_hooks.js create mode 100644 app/panels/clnt_task_board/index.js create mode 100644 app/panels/clnt_task_board/layouts.js create mode 100644 app/panels/clnt_task_board/styles.js create mode 100644 app/panels/clnt_task_board/task_dialog.js create mode 100644 db/PKG_P8PANELS_CLNTTSKBRD.pck diff --git a/app/panels/clnt_task_board/clnt_task_board.js b/app/panels/clnt_task_board/clnt_task_board.js new file mode 100644 index 0000000..095cc2b --- /dev/null +++ b/app/panels/clnt_task_board/clnt_task_board.js @@ -0,0 +1,316 @@ +/* + Парус 8 - Панели мониторинга - УДП - Доски задач + Панель мониторинга: Корневая панель доски задач +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React, { useEffect, useState, useCallback } from "react"; //Классы React +import { DragDropContext, Droppable } from "react-beautiful-dnd"; //Работа с drag&drop +import { Stack, Box, IconButton, Icon } from "@mui/material"; //Интерфейсные компоненты +import { StatusCard } from "./components/status_card.js"; +import { TaskDialog } from "./task_dialog.js"; //Компонент формы события +import { Filter } from "./filter.js"; //Компонент фильтров +import { useExtraData, useColorRules, useStatuses } from "./hooks/hooks.js"; //Вспомогательные хуки +import { useTasks } from "./hooks/tasks_hooks.js"; //Хук событий +import { useFilters } from "./hooks/filter_hooks.js"; //Вспомогательные хуки фильтра +import { NoteDialog } from "./components/note_dialog.js"; //Диалог примечания +import { SettingsDialog } from "./components/settings_dialog.js"; //Диалог дополнительных настроек +import { deepCopyObject } from "../../core/utils.js"; //Вспомогательные функции +import { COMMON_STYLES } from "./styles"; //Общие стили +import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Заголовок страницы + +//--------- +//Константы +//--------- + +//Высота фильтра +const FILTER_HEIGHT = "56px"; + +//Стили +const STYLES = { + CONTAINER: { width: "100%", padding: 0 }, + BOX_FILTER: { display: "flex", alignItems: "center" }, + ICON_BUTTON_SETTINGS: { marginLeft: "auto" }, + STACK_STATUSES: { maxWidth: "99vw", paddingBottom: "5px", overflowX: "auto", ...COMMON_STYLES.SCROLL }, + BOX_STATUSES: { position: "fixed", left: "8px", top: `calc(${APP_BAR_HEIGHT} + ${FILTER_HEIGHT})` } +}; + +//----------- +//Тело модуля +//----------- + +//Корневая панель доски задач +const ClntTaskBoard = () => { + //Состояние фильтров + const [filters, handleFiltersChange] = useFilters(); + + //Состояние текущего загруженного фильтра + const [filterTypeLoaded, setFilterTypeLoaded] = useState(filters.values.sType); + + //Состояние вспомогательных диалогов + const [dialogsState, setDialogsState] = useState({ + filterDialogIsOpen: filters.isSetByUser, + settingsDialogIsOpen: false, + noteDialog: { isOpen: false, callback: null }, + taskDialogIsOpen: false + }); + + //Состояние сортировок + const [orders, setOrders] = useState([]); + + //Состояние дополнительных данных + const [extraData, setExtraData, handleDocLinksLoad] = useExtraData(filters.values.sType); + + //Состояние статусов событий + const [statuses, statusesState, setStatuses, setStatusesState] = useStatuses(filters.values.sType); + + //Состояние пользовательских настроек заливки событий + const [colorRules, setColorRules] = useColorRules(); + + //Состояние событий + const [tasks, setTasks, onDragEnd] = useTasks(filters.values, orders); + + //Состояние доступных маршрутов события + const [availableRoutes, setAvailableRoutes] = useState({ source: "", routes: [] }); + + //Состояние перетаскиваемого события + const [dragItem, setDragItem] = useState({ type: "", status: "" }); + + //При открытии/закрытии диалога фильтра + const handleFilterOpen = isOpen => { + setDialogsState(pv => ({ ...pv, filterDialogIsOpen: isOpen })); + }; + + //При открытии/закрытии диалога дополнительных настроек + const handleSettingsOpen = () => setDialogsState(pv => ({ ...pv, settingsDialogIsOpen: !pv.settingsDialogIsOpen })); + + //При открытии/закрытии диалога примечания + const handleNoteOpen = (cb = null) => { + setDialogsState(pv => ({ ...pv, noteDialog: { isOpen: !dialogsState.noteDialog.isOpen, callback: cb ? v => cb(v) : null } })); + }; + + //При открытии/закрытии диалога события + const handleTaskDialogOpen = () => setDialogsState(pv => ({ ...pv, taskDialogIsOpen: !dialogsState.taskDialogIsOpen })); + + //При необходимости обновить дополнительные данные + const handleExtraDataReload = useCallback(() => { + setExtraData(pv => ({ ...pv, reload: true })); + }, [setExtraData]); + + //При необходимости обновить информацию о событиях + const handleTasksReload = useCallback( + (bAccountsReload = true) => { + setTasks(pv => ({ ...pv, reload: true, accountsReload: bAccountsReload })); + }, + [setTasks] + ); + + //При необходимости обновить состояние статусов + const handleStatusesStateReload = useCallback(() => { + setStatusesState(pv => ({ ...pv, reload: true, sorted: false })); + }, [setStatusesState]); + + //При изменении дополнительных настроек + const handleSettingsChange = (newSettings, statusesState) => { + setColorRules(pv => ({ ...pv, selectedColorRule: newSettings.selectedColorRule })); + setStatusesState({ ...statusesState, sorted: false }); + }; + + //При изменении цвета карточки статуса + const handleSettingStatusColorChange = (changedStatus, newColor) => { + //Считываем массив статусов + let newStatuses = [...statuses]; + //Изменяем цвет нужного статуса + newStatuses.find(status => status.ID === changedStatus.ID).color = newColor; + //Обновляем состояние + setStatuses([...newStatuses]); + }; + + //При изменении сортировки + const handleOrderChanged = columnName => { + //Копируем состояние сортировки + let newOrders = deepCopyObject(orders); + //Находим сортируемую колонку + const orderedColumn = newOrders.find(o => o.name == columnName); + //Определяем направление сортировки + const newDirection = orderedColumn?.direction == "ASC" ? "DESC" : orderedColumn?.direction == "DESC" ? null : "ASC"; + //Если сортировка отключается - очищаем информацию о сортировке + if (newDirection == null && orderedColumn) newOrders.splice(newOrders.indexOf(orderedColumn), 1); + //Если сортировки не было - устанавливаем + if (newDirection != null && !orderedColumn) newOrders.push({ name: columnName, direction: newDirection }); + //Если сортировка была и не отключается - изменяем + if (newDirection != null && orderedColumn) orderedColumn.direction = newDirection; + //Устанавливаем новую сортировку + setOrders(newOrders); + }; + + //При необходимости очистки доступных маршрутов события + const handleAvailableRoutesStateClear = () => { + setAvailableRoutes({ source: "", routes: [] }); + }; + + //Обработка захвата перетаскиваемого объекта + const handleDragItemChange = (filtersType, statusCode) => + setDragItem({ + type: filtersType, + status: statusCode + }); + + //Обработка очистки перетаскиваемого объекта + const handleDragItemClear = () => { + setDragItem({ type: "", status: "" }); + }; + + //Проверка доступности карточки события + const isCardAvailable = code => { + return availableRoutes.source === code || availableRoutes.routes.find(r => r.SDESTINATION === code) || !availableRoutes.source ? true : false; + }; + + //При изменении фильтра + useEffect(() => { + //Если изменился тип + if (filters.loaded && filters.values.sType) { + //Если тип события изменился + if (filterTypeLoaded !== filters.values.sType) { + //Обновляем информацию о дополнительных данных + handleExtraDataReload(); + //Обновляем информацию о статусах + handleStatusesStateReload(); + //Обновляем текущий загруженный тип события + setFilterTypeLoaded(filters.values.sType); + } + //Обновляем информацию о событиях + handleTasksReload(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [filters.loaded, filters.values]); + + //При изменении сортировки + useEffect(() => { + //Если есть все данные для загрузки событий + if (filters.loaded && filters.values.sType) { + //Обновляем информацию о событиях без обновления контрагентов + handleTasksReload(false); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [orders]); + + //Генерация содержимого + return ( + + {dialogsState.settingsDialogIsOpen ? ( + + ) : null} + {dialogsState.taskDialogIsOpen ? ( + handleTasksReload(true)} + onClose={() => { + handleTaskDialogOpen(); + handleDragItemClear(); + }} + /> + ) : null} + + + d.NRN === filters.values.sDocLink) : null} + onFilterChange={handleFiltersChange} + onDocLinksLoad={handleDocLinksLoad} + onFilterOpen={() => handleFilterOpen(true)} + onFilterClose={() => handleFilterOpen(false)} + onTasksReload={handleTasksReload} + orders={orders} + onOrderChanged={handleOrderChanged} + /> + + + settings + + + {dialogsState.noteDialog.isOpen ? ( + dialogsState.noteDialog.callback(note)} onClose={handleNoteOpen} /> + ) : null} + {filters.loaded && filters.values.sType && extraData.dataLoaded && tasks.loaded ? ( + { + //Поиск кода текущего статуса задачи + let sourceCode = statuses.find(status => status.ID == path.source.droppableId).SEVNSTAT_CODE; + //Устанавливаем доступные маршруты события + setAvailableRoutes({ source: sourceCode, routes: [...extraData.evRoutes.filter(route => route.SSOURCE === sourceCode)] }); + }} + onDragEnd={path => { + //Если есть статус назначения + if (path.destination) { + //Определяем мнемокод статуса назначения + let destCode = statuses.find(status => status.ID == path.destination.droppableId).SEVNSTAT_CODE; + //Переносим событие + onDragEnd({ path: path, eventPoints: extraData.evPoints, openNoteDialog: handleNoteOpen, destCode: destCode }); + } + //Очищаем информацию о доступных маршрутах события + handleAvailableRoutesStateClear(); + }} + > + + + {provided => ( +
+ + {statusesState.sorted + ? statuses.map((status, index) => ( +
+ + {provided => ( +
+ +
+ )} +
+
+ )) + : null} +
+ {provided.placeholder} +
+ )} +
+
+
+ ) : null} +
+ ); +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { ClntTaskBoard }; diff --git a/app/panels/clnt_task_board/components/custom_input_field.js b/app/panels/clnt_task_board/components/custom_input_field.js new file mode 100644 index 0000000..e003ee7 --- /dev/null +++ b/app/panels/clnt_task_board/components/custom_input_field.js @@ -0,0 +1,174 @@ +/* + Парус 8 - Панели мониторинга - УДП - Доски задач + Компонент: Кастомное поле ввода +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React, { useEffect, useState } from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента +import { FormControl, InputLabel, Input, InputAdornment, IconButton, Icon, FormHelperText, Select, MenuItem, Typography } from "@mui/material"; //Интерфейсные компоненты +import { COMMON_STYLES } from "../styles"; //Общие стили + +//--------- +//Константы +//--------- + +//Стили +const STYLES = { + HELPER_TEXT: { color: "red" }, + SELECT_MENU: width => { + return { ...COMMON_STYLES.SCROLL, width: width ? width + 24 : null }; + } +}; + +//--------------- +//Тело компонента +//--------------- + +//Кастомное поле ввода +const CustomInputField = ({ + elementCode, + elementValue, + labelText, + onChange, + required = false, + items = null, + emptyItem = null, + dictionary, + menuItemRender, + ...other +}) => { + //Значение элемента + const [value, setValue] = useState(elementValue); + + //Состояние элемента HTML (для оптимизации ширины MenuItems) + const [anchorEl, setAnchorEl] = useState(); + + //При открытии меню заливки событий + const handleMenuOpen = e => { + //Устанавливаем элемент меню + setAnchorEl(e.target); + }; + + //При получении нового значения из вне + useEffect(() => { + setValue(elementValue); + }, [elementValue]); + + //Изменение значения элемента + const handleChange = e => { + setValue(e.target.value); + if (onChange) onChange(e.target.name, e.target.value); + }; + + //Выбор значения из словаря + const handleDictionaryClick = () => { + dictionary ? dictionary(res => (res ? handleChange({ target: { name: elementCode, value: res } }) : null)) : null; + }; + + //Генерация поля с выбором из словаря Парус + const renderInput = validationError => { + //Генерация содержимого + return ( + + + list + + + ) : null + } + aria-describedby={`${elementCode}-helper-text`} + label={labelText} + onChange={handleChange} + {...other} + /> + ); + }; + + //Генерация поля с выпадающим списком + const renderSelect = (items, anchorEl, handleMenuOpen, validationError) => { + //Формируем общий список элементов меню + const menuItems = emptyItem ? [emptyItem, ...items] : [...items]; + //Генерация содержимого + return ( + + ); + }; + + //Признак ошибки валидации + const validationError = !value && required ? true : false; + + //Генерация содержимого + return ( + + {labelText} + {items ? renderSelect(items, anchorEl, handleMenuOpen, validationError) : renderInput(validationError)} + {validationError ? ( + + *Обязательное поле + + ) : null} + + ); +}; + +//Контроль свойств - Кастомное поле ввода +CustomInputField.propTypes = { + elementCode: PropTypes.string.isRequired, + elementValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + labelText: PropTypes.string.isRequired, + required: PropTypes.bool, + items: PropTypes.arrayOf(PropTypes.object), + emptyItem: PropTypes.object, + dictionary: PropTypes.func, + onChange: PropTypes.func, + menuItemRender: PropTypes.func +}; + +//-------------------- +//Интерфейс компонента +//-------------------- + +export { CustomInputField }; diff --git a/app/panels/clnt_task_board/components/filter_dialog.js b/app/panels/clnt_task_board/components/filter_dialog.js new file mode 100644 index 0000000..4b5b661 --- /dev/null +++ b/app/panels/clnt_task_board/components/filter_dialog.js @@ -0,0 +1,336 @@ +/* + Парус 8 - Панели мониторинга - УДП - Доски задач + Компонент: Диалог фильтра отбора +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React, { useState, useContext, useEffect, useCallback } from "react"; //Классы React +import { BackEndСtx } from "../../../context/backend"; //Контекст взаимодействия с сервером +import PropTypes from "prop-types"; //Контроль свойств компонента +import { + Dialog, + DialogTitle, + IconButton, + Icon, + DialogContent, + DialogActions, + Button, + Box, + Stack, + Checkbox, + FormControlLabel, + Radio, + RadioGroup +} from "@mui/material"; //Интерфейсные компоненты +import { CustomInputField } from "./custom_input_field"; //Кастомное поле ввода +import { hasValue } from "../../../core/utils"; //Вспомогательные функции +import { EVENT_STATES } from "../layouts"; //Перечисление состояний события +import { COMMON_STYLES } from "../styles"; //Общие стили +import { useDictionary } from "../hooks/dict_hooks"; //Состояние открытия разделов + +//--------- +//Константы +//--------- + +//Стили +const STYLES = { + SELECT: { width: "450px" } +}; + +//--------------- +//Тело компонента +//--------------- + +//Диалог фильтра отбора +const FilterDialog = ({ initial, onFilterChange, onFilterClose, onDocLinksLoad }) => { + //Собственное состояние + const [filter, setFilter] = useState(initial.filter); + + //Состояние текущих учётных документов + const [curDocLinks, setCurDocLinks] = useState({ loaded: true, docLinks: initial.docLinks }); + + //Вспомогательные функции открытия раздела + const { handleCatalogTreeOpen, handleEventTypesOpen, handleAgnlistOpen, handleInsDepartmentOpen, handleCostStaffGroupsOpen } = useDictionary(); + + //Подключение к контексту взаимодействия с сервером + const { executeStored } = useContext(BackEndСtx); + + //При изменении типа события фильтра + const handleTypeChange = callBack => + handleEventTypesOpen({ + sCode: filter.sType, + callBack: res => { + callBack(res.outParameters.eventtypecode); + } + }); + + //При изменении каталога фильтра + const handleCrnChange = callBack => + handleCatalogTreeOpen({ + sUnitName: "ClientEvents", + sName: filter.sCrnName, + callBack: res => { + callBack(res.outParameters.out_NAME); + } + }); + + //При изменении исполнителя фильтра + const handleSendPersonChange = callBack => + handleAgnlistOpen({ + sMnemo: filter.sSendPerson, + callBack: res => { + callBack(res.outParameters.agnmnemo); + } + }); + + //При изменении подразделения фильтра + const handleSendDivisionChange = callBack => + handleInsDepartmentOpen({ + sCode: filter.sSendDivision, + callBack: res => { + callBack(res.outParameters.out_CODE); + } + }); + + //При изменении группы пользователей фильтра + const handleSendUsrGrpChange = callBack => + handleCostStaffGroupsOpen({ + sCode: filter.sSendUsrGrp, + callBack: res => { + callBack(res.outParameters.out_CODE); + } + }); + + //Считывание подкаталогов + const getSubCatalogs = useCallback(async () => { + //Считываем каталоги + const data = await executeStored({ + stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_SUBCATALOGS_GET", + args: { + SCRN_NAME: filter.sCrnName, + NSUBCAT: filter.bSubcatalogs ? 1 : 0 + } + }); + //Возвращаем список каталогов + return data.SRESULT; + }, [executeStored, filter.sCrnName, filter.bSubcatalogs]); + + //При закрытии диалога с изменением фильтра + const handleDialogOk = async () => { + //Если указано имя каталога, но не загружен список рег. номеров + if (filter.sCrnName && !filter.sCrnRnList) { + //Загружаем список рег. номеров каталогов + const crns = await getSubCatalogs(); + //Устанавливаем новый фильтр + onFilterChange({ ...filter, ...(crns ? { sCrnRnList: crns } : {}) }); + //Закрываем диалог фильтра + onFilterClose(); + } else { + //Устанавливаем новый фильтр + onFilterChange(filter); + //Закрываем диалог фильтра + onFilterClose(); + } + }; + + //При очистке фильтра + const handleFilterClear = () => { + setFilter({ + sState: EVENT_STATES[1], + sType: "", + sCrnName: "", + sCrnRnList: "", + bSubcatalogs: false, + sSendPerson: "", + sSendDivision: "", + sSendUsrGrp: "", + sDocLink: "" + }); + }; + + //При изменении значения элемента + const handleFilterItemChange = (item, value) => { + //Если это изменение типа + if (item === "sType") { + //Указываем тип с очисткой информации об учетных документах + setFilter(pv => ({ ...pv, [item]: value, sDocLink: "" })); + setCurDocLinks(pv => ({ ...pv, loaded: false, docLinks: [] })); + } else { + //Обновляем значение поля + setFilter(pv => ({ ...pv, [item]: value })); + } + }; + + //При очистке учётного документа + const handleDocLinkClear = () => setFilter(pv => ({ ...pv, sDocLink: "" })); + + //Обработка изменений с каталогами + useEffect(() => { + //Если каталог не указан, но галка подкаталогов установлена - снимаем её + if (!filter.sCrnName && filter.bSubcatalogs) setFilter(pv => ({ ...pv, bSubcatalogs: false })); + //Если изменился каталог и остался список рег. номеров каталогов - очищаем его + if (filter.sCrnName !== initial.sCrnName && filter.sCrnRnList) setFilter(pv => ({ ...pv, sCrnRnList: "" })); + //Если каталог равен изначальному + if (filter.sCrnName === initial.sCrnName) { + //Если признак подкаталогов равен изначальному, но список рег. номеров каталогов не соответствует - загружаем изначальный + if (filter.bSubcatalogs === initial.bSubcatalogs && filter.sCrnRnList !== initial.sCrnRnList) { + setFilter(pv => ({ ...pv, sCrnRnList: initial.sCrnRnList })); + } + //Если признак подкаталогов не равен изначальному + if (filter.bSubcatalogs !== initial.bSubcatalogs) { + //Если не установлен - считываем первый из списка рег. номеров изначальных каталогов + if (!filter.bSubcatalogs) { + setFilter(pv => ({ + ...pv, + sCrnRnList: initial.sCrnRnList.split(";")[0] + })); + } else { + //Если установлен - очищаем список рег. номеров каталогов для последующей загрузки + setFilter(pv => ({ ...pv, sCrnRnList: "" })); + } + } + } + }, [filter.sCrnName, filter.sCrnRnList, filter.bSubcatalogs, initial.sCrnName, initial.sCrnRnList, initial.bSubcatalogs]); + + //Генерация содержимого + return ( +
+ + Фильтр отбора + + close + + + + Состояние + handleFilterItemChange(e.target.name, e.target.value)} + > + {Object.keys(EVENT_STATES).map(function (k) { + return } label={EVENT_STATES[k]} />; + })} + + + + handleTypeChange(callBack)} + onChange={handleFilterItemChange} + /> + + + handleCrnChange(callBack)} + onChange={handleFilterItemChange} + /> + handleFilterItemChange(e.target.name, e.target.checked)} + /> + } + label="Включая подкаталоги" + /> + + + handleSendPersonChange(callBack)} + onChange={handleFilterItemChange} + /> + + + handleSendDivisionChange(callBack)} + onChange={handleFilterItemChange} + /> + + + handleSendUsrGrpChange(callBack)} + onChange={handleFilterItemChange} + /> + + + + [...prev, { id: cur.NRN, caption: cur.SDESCR }], [])} + disabled={!curDocLinks.docLinks.length ? true : false} + onChange={handleFilterItemChange} + sx={STYLES.SELECT} + /> + + clear + + { + //Очищаем учетный документ + handleDocLinkClear(); + //Загружаем учетные документы типа + onDocLinksLoad(filter.sType).then(dl => setCurDocLinks(pv => ({ ...pv, loaded: true, docLinks: dl }))); + }} + > + refresh + + + + + + + + + + +
+ ); +}; + +//Контроль свойств компонента - Диалог фильтра отбора +FilterDialog.propTypes = { + initial: PropTypes.object.isRequired, + onFilterChange: PropTypes.func.isRequired, + onFilterClose: PropTypes.func.isRequired, + onDocLinksLoad: PropTypes.func +}; + +//-------------------- +//Интерфейс компонента +//-------------------- + +export { FilterDialog }; diff --git a/app/panels/clnt_task_board/components/note_dialog.js b/app/panels/clnt_task_board/components/note_dialog.js new file mode 100644 index 0000000..2977121 --- /dev/null +++ b/app/panels/clnt_task_board/components/note_dialog.js @@ -0,0 +1,99 @@ +/* + Парус 8 - Панели мониторинга - УДП - Доски задач + Компонент: Диалог примечания +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React, { useState } from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента +import { Dialog, DialogTitle, IconButton, Icon, DialogContent, DialogActions, Button, TextField } from "@mui/material"; //Интерфейсные компоненты +import { CustomInputField } from "./custom_input_field.js"; //Кастомное поле ввода +import { COMMON_STYLES } from "../styles"; //Общие стили + +//Стили +const STYLES = { + DIALOG_CONTENT: { paddingTop: 0, paddingBottom: 0 } +}; + +//--------------- +//Тело компонента +//--------------- + +//Диалог примечания +const NoteDialog = ({ noteTypes, onCallback, onClose }) => { + //Собственное состояние + const [note, setNote] = useState({ noteTypeIndex: 0, text: "" }); + + //При изменении примечания + const handleNoteChange = value => setNote(pv => ({ ...pv, text: value })); + + //При изменении заголовка примечания + const handleNoteHeaderChange = (name, value) => { + setNote(pv => ({ ...pv, noteTypeIndex: value })); + }; + + //При закрытии диалога с изменением примечания + const handleDialogOk = () => { + //Передаем информацию о примечание в callback + onCallback({ header: noteTypes[note.noteTypeIndex], text: note.text }); + onClose(); + }; + + //Генерация содержимого + return ( + + Примечание + + close + + + [...prev, { id: prev.length, caption: cur }], [])} + onChange={handleNoteHeaderChange} + margin="dense" + /> + handleNoteChange(e.target.value)} + /> + + + + + + + ); +}; + +//Контроль свойств - Диалог примечания +NoteDialog.propTypes = { + noteTypes: PropTypes.array, + onCallback: PropTypes.func.isRequired, + onClose: PropTypes.func.isRequired +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { NoteDialog }; diff --git a/app/panels/clnt_task_board/components/settings_dialog.js b/app/panels/clnt_task_board/components/settings_dialog.js new file mode 100644 index 0000000..eb8420d --- /dev/null +++ b/app/panels/clnt_task_board/components/settings_dialog.js @@ -0,0 +1,131 @@ +/* + Парус 8 - Панели мониторинга - УДП - Доски задач + Компонент: Диалог дополнительных настроек +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React, { useState } from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента +import { Dialog, DialogTitle, DialogContent, DialogActions, IconButton, Icon, Button, Box, Stack } from "@mui/material"; //Интерфейсные компоненты +import { CustomInputField } from "./custom_input_field.js"; //Кастомное поле ввода +import { sortAttrs, sortDest } from "../layouts.js"; //Допустимые значение поля и направления сортировки +import { hasValue } from "../../../core/utils.js"; //Проверка наличия значения +import { COMMON_STYLES } from "../styles"; //Общие стили + +//--------- +//Константы +//--------- + +//Стили +const STYLES = { + SELECT: { width: "100%" } +}; + +//--------------- +//Тело компонента +//--------------- + +//Диалог дополнительных настроек +const SettingsDialog = ({ initial, onSettingsChange, onClose, ...other }) => { + //Состояние дополнительных настроек + const [colorRules, seColorRules] = useState(initial.colorRules); + + //Состояние статусов + const [statusesState, setStatusesState] = useState(initial.statusesState); + + //Изменение поля сортировки + const handleSortAttrChange = (item, value) => setStatusesState(pv => ({ ...pv, [item]: value })); + + //Изменение направления сортировки + const handleSortDestChange = newDirection => setStatusesState(pv => ({ ...pv, direction: newDirection })); + + //При изменении правила заливки событий + const handleColorRuleChange = (item, value) => { + //Определяем новое правило заливки + let newColorRule = colorRules.rules[value]; + //Обновляем в основных настройках + seColorRules(pv => ({ ...pv, selectedColorRule: newColorRule ? newColorRule : {} })); + }; + + //Генерация содержимого + return ( +
+ + Настройки + + close + + + + [...prev, { id: cur.id, caption: cur.SDP_NAME }], [])} + emptyItem={{ key: -1, id: -1, caption: "-" }} + onChange={handleColorRuleChange} + sx={STYLES.SELECT} + /> + + + + [...prev, { id: cur.id, caption: cur.descr }], [])} + onChange={handleSortAttrChange} + sx={STYLES.SELECT} + /> + handleSortDestChange(sortDest[sortDest.indexOf(statusesState.direction) * -1])} + > + {statusesState.direction === "asc" ? "arrow_upward" : "arrow_downward"} + + + + + + + + + + +
+ ); +}; + +//Контроль свойств компонента - Диалог дополнительных настроек +SettingsDialog.propTypes = { + initial: PropTypes.object.isRequired, + onSettingsChange: PropTypes.func.isRequired, + onClose: PropTypes.func.isRequired +}; + +//-------------------- +//Интерфейс компонента +//-------------------- + +export { SettingsDialog }; diff --git a/app/panels/clnt_task_board/components/status_card.js b/app/panels/clnt_task_board/components/status_card.js new file mode 100644 index 0000000..b3b851a --- /dev/null +++ b/app/panels/clnt_task_board/components/status_card.js @@ -0,0 +1,182 @@ +/* + Парус 8 - Панели мониторинга - УДП - Доски задач + Компонент: Карточка статуса событий +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React, { useState } from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента +import { Card, CardHeader, CardContent, Button, IconButton, Icon, Typography, Stack } from "@mui/material"; //Интерфейсные компоненты +import { TaskCard } from "./task_card.js"; //Компонент Карточка события +import { StatusCardSettings } from "./status_card_settings.js"; //Компонент Диалог настройки карточки событий +import { APP_STYLES } from "../../../../app.styles"; //Типовые стили +import { COLORS } from "../layouts.js"; //Цвета статусов +import { APP_BAR_HEIGHT } from "../../../components/p8p_app_workspace"; //Заголовок страницы + +//--------- +//Константы +//--------- + +//Нижний отступ заголовка +const TITLE_PADDING_BOTTOM = "16px"; + +//Высота фильтра +const FILTER_HEIGHT = "56px"; + +//Стили +const STYLES = { + STATUS_BLOCK: statusColor => { + return { + width: "350px", + height: `calc(100vh - ${APP_BAR_HEIGHT} - ${TITLE_PADDING_BOTTOM} - ${FILTER_HEIGHT} - 8px)`, + backgroundColor: statusColor, + padding: "8px" + }; + }, + BLOCK_OPACITY: isAvailable => { + return isAvailable ? { opacity: 1 } : { opacity: 0.5 }; + }, + CARD_HEADER_TITLE: { + textAlign: "left", + textOverflow: "ellipsis", + overflow: "hidden", + display: "-webkit-box", + hyphens: "auto", + WebkitBoxOrient: "vertical", + WebkitLineClamp: 1, + maxWidth: "calc(300px)", + width: "-webkit-fill-available", + fontSize: "1.2rem", + cursor: "default" + }, + CARD_HEADER: { padding: 0 }, + CARD_CONTENT: { + padding: 0, + paddingRight: "5px", + paddingBottom: "5px !important", + overflowY: "auto", + maxHeight: `calc(100vh - ${APP_BAR_HEIGHT} - ${TITLE_PADDING_BOTTOM} - ${FILTER_HEIGHT} - 85px)`, + ...APP_STYLES.SCROLL + } +}; + +//--------------- +//Тело компонента +//--------------- + +//Карточка статуса события +const StatusCard = ({ + tasks, + status, + statusTitle, + colorRules, + extraData, + filtersType, + isCardAvailable, + onTasksReload, + onDragItemChange, + onTaskDialogOpen, + onNoteDialogOpen, + onStatusColorChange, + placeholder +}) => { + //Состояние диалога настройки + const [statusCardSettingsOpen, setStatusCardSettingsOpen] = useState(false); + + //Открыть/закрыть диалог настройки + const handleStatusCardSettingsOpen = () => setStatusCardSettingsOpen(!statusCardSettingsOpen); + + //При изменении цвета статуса + const handleStatusColorChange = newColor => { + onStatusColorChange(status, newColor); + }; + + //Генерация содержимого + return ( +
+ {statusCardSettingsOpen ? ( + + ) : null} + + + more_vert + + } + title={ + + {statusTitle} + + } + subheader={ + + } + sx={STYLES.CARD_HEADER} + /> + + + {tasks.rows + .filter(item => item.sStatus === status.SEVNSTAT_NAME) + .map((item, index) => ( + p.SEVPOINT === status.SEVNSTAT_CODE)} + onOpenNoteDialog={onNoteDialogOpen} + /> + ))} + {placeholder} + + + +
+ ); +}; + +//Контроль свойств - Карточка статуса события +StatusCard.propTypes = { + tasks: PropTypes.object.isRequired, + status: PropTypes.object.isRequired, + statusTitle: PropTypes.string.isRequired, + colorRules: PropTypes.object.isRequired, + extraData: PropTypes.object.isRequired, + filtersType: PropTypes.string.isRequired, + isCardAvailable: PropTypes.func.isRequired, + onTasksReload: PropTypes.func.isRequired, + onDragItemChange: PropTypes.func.isRequired, + onTaskDialogOpen: PropTypes.func.isRequired, + onNoteDialogOpen: PropTypes.func.isRequired, + onStatusColorChange: PropTypes.func.isRequired, + placeholder: PropTypes.object.isRequired +}; + +//-------------------- +//Интерфейс компонента +//-------------------- + +export { StatusCard }; diff --git a/app/panels/clnt_task_board/components/status_card_settings.js b/app/panels/clnt_task_board/components/status_card_settings.js new file mode 100644 index 0000000..50ece2f --- /dev/null +++ b/app/panels/clnt_task_board/components/status_card_settings.js @@ -0,0 +1,109 @@ +/* + Парус 8 - Панели мониторинга - УДП - Доски задач + Компонент: Диалог настройки карточки статуса +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React, { useState } from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента +import { Dialog, DialogTitle, IconButton, Icon, DialogContent, DialogActions, Button, Box, MenuItem, Typography } from "@mui/material"; //Интерфейсные компоненты +import { CustomInputField } from "./custom_input_field.js"; //Кастомное поле ввода +import { COMMON_STYLES } from "../styles"; //Общие стили + +//--------- +//Константы +//--------- + +//Стили +const STYLES = { + BCKG_COLOR: backgroundColor => ({ backgroundColor: backgroundColor }) +}; + +//-------------------------------- +//Вспомогательные классы и функции +//-------------------------------- + +//Генерация элемента меню +const menuItemRender = ({ item, key }) => { + //Генерация содержимого + return ( + + + {item.caption} + + + ); +}; + +//--------------- +//Тело компонента +//--------------- + +//Диалог настройки карточки статуса +const StatusCardSettings = ({ statusColor, availableColors, onClose, onColorChange }) => { + //Состояние индекса текущего цвета + const [colorIndex, setColorIndex] = useState(availableColors.indexOf(statusColor)); + + //При закрытии диалога с применением настройки статуса + const handleDialogOk = () => { + //Изменяем цвет статуса + onColorChange(availableColors[colorIndex]); + //Закрываем диалог + onClose(); + }; + + //При изменении значения элемента + const handleSettingsItemChange = (item, value) => { + setColorIndex(value); + }; + + //Генерация содержимого + return ( +
+ + Настройки + + close + + + + [...prev, { id: prev.length, caption: cur }], [])} + onChange={handleSettingsItemChange} + sx={STYLES.BCKG_COLOR(availableColors[colorIndex])} + menuItemRender={menuItemRender} + /> + + + + + + + +
+ ); +}; + +//Контроль свойств - Диалог настройки карточки статуса +StatusCardSettings.propTypes = { + statusColor: PropTypes.string.isRequired, + availableColors: PropTypes.arrayOf(PropTypes.string).isRequired, + onClose: PropTypes.func.isRequired, + onColorChange: PropTypes.func.isRequired +}; + +//-------------------- +//Интерфейс компонента +//-------------------- + +export { StatusCardSettings }; diff --git a/app/panels/clnt_task_board/components/task_card.js b/app/panels/clnt_task_board/components/task_card.js new file mode 100644 index 0000000..953a2f2 --- /dev/null +++ b/app/panels/clnt_task_board/components/task_card.js @@ -0,0 +1,376 @@ +/* + Парус 8 - Панели мониторинга - УДП - Доски задач + Компонент: Карточка события +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React, { useState, useContext, useCallback, useEffect } from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента +import { Draggable } from "react-beautiful-dnd"; //Работа с drag&drop +import { Card, CardHeader, Typography, IconButton, Icon, Box, Menu, MenuItem, CardContent, Avatar, Stack } from "@mui/material"; //Интерфейсные компоненты +import { TaskDialog } from "../task_dialog"; //Форма события +import { ApplicationСtx } from "../../../context/application"; //Контекст приложения +import { BackEndСtx } from "../../../context/backend"; //Контекст взаимодействия с сервером +import { MessagingСtx } from "../../../context/messaging"; //Контекст сообщений +import { TASK_COLORS, getTaskExpiredColor, getTaskBgColorByRule, makeCardActionsArray } from "../layouts"; //Дополнительная разметка и вёрстка клиентских элементов +import { useDictionary } from "../hooks/dict_hooks"; //Состояние открытия разделов +import { useTasksFunctions } from "../hooks/tasks_hooks"; //Состояние вспомогательных функций событий + +//--------- +//Константы +//--------- + +//Стили +const STYLES = { + MENU_ITEM_DELIMITER: { borderBottom: "1px solid lightgrey" }, + CARD: (indicatorClr, bgClr) => { + const i = indicatorClr ? { borderLeft: `solid ${indicatorClr}` } : null; + const bc = bgClr ? { backgroundColor: bgClr } : null; + return { ...i, ...bc }; + }, + CARD_HEADER_TITLE: { + padding: "4px", + width: "292px", + display: "-webkit-box", + hyphens: "auto", + WebkitBoxOrient: "vertical", + WebkitLineClamp: 2, + overflow: "hidden" + }, + CARD_HEADER: { padding: 0, cursor: "pointer" }, + CARD_CONTENT: { padding: "4px !important" }, + CARD_CONTENT_BOX: { display: "flex", alignItems: "center" }, + STACK_SENDER: { alignItems: "center", marginLeft: "auto" }, + TYPOGRAPHY_SECONDARY: { + color: "text.secondary", + fontSize: 14 + }, + ICON_COLOR: linked => { + return { color: theme => (linked ? TASK_COLORS.LINKED : theme.palette.grey[500]) }; + } +}; + +//------------------------------------ +//Вспомогательные функции и компоненты +//------------------------------------ + +//Действия карточки события +const CardActions = ({ + taskRn, + menuItems, + cardActions, + onMethodsMenuButtonClick, + onMethodsMenuClose, + onTasksReload, + pointSettings, + onOpenNoteDialog +}) => { + //При нажатии на действие меню + const handleActionClick = action => { + //Выполняем действие + action.func({ + nEvent: taskRn, + onReload: action.tasksReload ? () => onTasksReload(action.needAccountsReload) : null, + onNoteOpen: pointSettings.ADDNOTE_ONSEND ? onOpenNoteDialog : null + }); + //Закрываем меню действий + onMethodsMenuClose(); + }; + + return ( + + + more_vert + + + {menuItems.map(action => { + if (action.visible) + return ( + handleActionClick(action)} + > + {action.icon} + {action.name} + + ); + })} + + + ); +}; + +//Контроль свойств - Действия карточки события +CardActions.propTypes = { + taskRn: PropTypes.number.isRequired, + menuItems: PropTypes.array.isRequired, + cardActions: PropTypes.object.isRequired, + onMethodsMenuButtonClick: PropTypes.func.isRequired, + onMethodsMenuClose: PropTypes.func.isRequired, + onTasksReload: PropTypes.func, + pointSettings: PropTypes.object, + onOpenNoteDialog: PropTypes.func +}; + +//----------- +//Тело модуля +//----------- + +//Карточка события +const TaskCard = ({ task, index, onTasksReload, colorRule, pointSettings, onOpenNoteDialog }) => { + //Состояние диалога события + const [taskDialogOpen, setTaskDialogOpen] = useState(false); + + //Состояние действий события + const [cardActions, setCardActions] = useState({ anchorMenuMethods: null, openMethods: false }); + + //Состояние списка действий меню + const [menuItems, setMenuItems] = useState([]); + + //Вспомогательные функции открытия раздела + const { handleClientEventsOpen, handleClientEventsNotesOpen, handleFileLinksOpen } = useDictionary(); + + //Состояние вспомогательных функций событий + const { handleTaskStateChange, handleTaskSend } = useTasksFunctions(); + + //Подключение к контексту взаимодействия с сервером + const { executeStored } = useContext(BackEndСtx); + + //Подключение к контексту сообщений + const { showMsgWarn } = useContext(MessagingСtx); + + //Подключение к контексту приложения + const { pOnlineShowDocument } = useContext(ApplicationСtx); + + //По нажатию на открытие меню действий + const handleMethodsMenuButtonClick = useCallback(event => { + setCardActions(pv => ({ ...pv, anchorMenuMethods: event.currentTarget, openMethods: true })); + }, []); + + //При закрытии меню + const handleMethodsMenuClose = useCallback(() => { + setCardActions(pv => ({ ...pv, anchorMenuMethods: null, openMethods: false })); + }, []); + + //При удалении контрагента + const handleTaskDelete = useCallback( + async ({ nEvent, onReload }) => { + await executeStored({ + stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_DELETE", + args: { NCLNEVENTS: nEvent } + }); + //Если требуется перезагрузить данные + onReload ? onReload() : null; + }, + [executeStored] + ); + + //При возврате в предыдущую точку события + const handleTaskReturn = useCallback( + async ({ nEvent, onReload }) => { + await executeStored({ + stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_RETURN", + args: { NCLNEVENTS: nEvent } + }); + //Если требуется перезагрузить данные + onReload ? onReload() : null; + }, + [executeStored] + ); + + //По нажатию действия "Направить" + const handleSendAction = useCallback( + async ({ nEvent, onReload, onNoteOpen }) => { + //Выполняем направление события + handleTaskSend({ nEvent, onReload, onNoteOpen }); + }, + [handleTaskSend] + ); + + //По нажатия действия "Редактировать" + const handleTaskEditAction = useCallback(() => { + setTaskDialogOpen(true); + }, []); + + //По нажатия действия "Редактировать в разделе" + const handleTaskEditClientAction = useCallback( + async ({ nEvent }) => { + const data = await executeStored({ + stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_SELECT", + args: { + NCLNEVENTS: nEvent + } + }); + if (data.NIDENT) { + //Открываем раздел "События" с фильтром по записи + handleClientEventsOpen({ nIdent: data.NIDENT }); + } + }, + [executeStored, handleClientEventsOpen] + ); + + //По нажатию действия "Удалить" + const handleTaskDeleteAction = useCallback( + ({ nEvent, onReload }) => { + showMsgWarn("Удалить событие?", () => handleTaskDelete({ nEvent, onReload })); + }, + [handleTaskDelete, showMsgWarn] + ); + + //По нажатию действия "Выполнить возврат" + const handleTaskReturnAction = useCallback( + ({ nEvent, onReload }) => { + showMsgWarn("Выполнить возврат события в предыдущую точку?", () => handleTaskReturn({ nEvent, onReload })); + }, + [handleTaskReturn, showMsgWarn] + ); + + //По нажатию действия "Примечания" + const handleEventNotesOpenAction = useCallback( + ({ nEvent }) => { + handleClientEventsNotesOpen({ nPrn: nEvent }); + }, + [handleClientEventsNotesOpen] + ); + + //По нажатию действия "Присоединенные документы" + const handleTaskFileLinksOpenAction = useCallback( + ({ nEvent }) => { + handleFileLinksOpen({ nPrn: nEvent, sUnitCode: "ClientEvents" }); + }, + [handleFileLinksOpen] + ); + + //По нажатию действия "Перейти" + const handleStateChangeAction = useCallback( + async ({ nEvent, onReload, onNoteOpen }) => { + //Выполняем изменения статуса события + handleTaskStateChange({ nEvent, onReload, onNoteOpen }); + }, + [handleTaskStateChange] + ); + + //При изменении ссылок в меню действий (для того, чтобы ссылка на объект менялась при реальной необходимости) + useEffect(() => { + //Устанавливаем список меню + setMenuItems( + makeCardActionsArray( + handleTaskEditAction, + handleTaskEditClientAction, + handleTaskDeleteAction, + handleStateChangeAction, + handleTaskReturnAction, + handleSendAction, + handleEventNotesOpenAction, + handleTaskFileLinksOpenAction + ) + ); + }, [ + handleEventNotesOpenAction, + handleTaskFileLinksOpenAction, + handleSendAction, + handleStateChangeAction, + handleTaskDeleteAction, + handleTaskEditAction, + handleTaskEditClientAction, + handleTaskReturnAction + ]); + + //Генерация содержимого + return ( + + {taskDialogOpen ? ( + { + setTaskDialogOpen(false); + }} + /> + ) : null} + + {provided => ( + + { + menuItems.find(action => + action.method === "EDIT" ? action.func(task.nRn, action.tasksReload ? onTasksReload : null) : null + ); + }} + > + {task.sDescription} + + } + sx={STYLES.CARD_HEADER} + action={ + + } + /> + + + pOnlineShowDocument({ unitCode: task.sLinkedUnit, document: task.nLinkedRn }) : null + } + sx={STYLES.ICON_COLOR(task.nLinkedRn)} + disabled={!task.nLinkedRn} + > + assignment + + {task.name} + {task.sSender ? ( + + {task.sSender} + + + ) : null} + + + + )} + + + ); +}; + +//Контроль свойств - Карточка события +TaskCard.propTypes = { + task: PropTypes.object.isRequired, + index: PropTypes.number.isRequired, + onTasksReload: PropTypes.func, + colorRule: PropTypes.object, + pointSettings: PropTypes.object, + onOpenNoteDialog: PropTypes.func +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { TaskCard }; diff --git a/app/panels/clnt_task_board/components/task_form.js b/app/panels/clnt_task_board/components/task_form.js new file mode 100644 index 0000000..12d5882 --- /dev/null +++ b/app/panels/clnt_task_board/components/task_form.js @@ -0,0 +1,158 @@ +/* + Парус 8 - Панели мониторинга - УДП - Доски задач + Компонент панели: Форма события +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React, { useState, useEffect, useCallback } from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента +import { Box, Typography, Tabs, Tab, InputAdornment, IconButton, Icon } from "@mui/material"; //Интерфейсные компоненты +import { TaskFormTabInfo } from "./task_form_tab_info"; //Вкладка основной информации +import { TaskFormTabExecutor } from "./task_form_tab_executor"; //Вкладка информации об исполнителе +import { TaskFormTabProps } from "./task_form_tab_props"; //Вкладка информации со свойствами +import { useDocsProps } from "../hooks/task_dialog_hooks"; //Хук для получения свойств раздела "События" +import { hasValue } from "../../../core/utils"; + +//--------- +//Константы +//--------- + +//Стили +const STYLES = { + CONTAINER: { margin: "5px 0px", textAlign: "center" } +}; + +//------------------------------------ +//Вспомогательные функции и компоненты +//------------------------------------ + +//Свойства вкладки +function a11yProps(index) { + return { + id: `simple-tab-${index}`, + "aria-controls": `simple-tabpanel-${index}` + }; +} + +//Вкладка информации +function CustomTabPanel(props) { + const { children, value, index, ...other } = props; + //Генерация содержимого + return ( + + ); +} + +//Контроль свойств - Вкладка информации +CustomTabPanel.propTypes = { + children: PropTypes.node, + index: PropTypes.number.isRequired, + value: PropTypes.number.isRequired +}; + +//Формирование кнопки для открытия раздела +export const getInputProps = (onClick, disabled = false, icon = "list") => { + //Генерация содержимого + return { + endAdornment: ( + + + {icon} + + + ) + }; +}; + +//----------- +//Тело модуля +//----------- + +//Форма события +const TaskForm = ({ task, taskType, onTaskChange, editable, onEventNextNumbGet, onDPReady }) => { + //Состояние вкладки + const [tab, setTab] = useState(0); + + //Состояние допустимых дополнительных свойств + const [docProps] = useDocsProps(taskType); + + //При изменении вкладки + const handleTabChange = (e, newValue) => { + setTab(newValue); + }; + + //При изменении поля + const handleFieldEdit = useCallback( + e => { + onTaskChange({ + [e.target.id]: e.target.value, + //Связанные значения, если меняется одно, то необходимо обнулить другое + ...(e.target.id === "sClntClnperson" ? { sClntClients: "" } : {}), + ...(e.target.id === "sClntClients" ? { sClntClnperson: "" } : {}) + }); + }, + [onTaskChange] + ); + + //При изменении доп. свойства + const handlePropEdit = useCallback( + (docProp, value) => { + onTaskChange({ docProps: { ...task.docProps, [docProp]: value } }); + }, + [onTaskChange, task.docProps] + ); + + //Проверка заполненности всех обязательных доп. свойств + useEffect(() => { + //Считываем количество незаполненных обязательных свойств + let notFilled = docProps.props.filter(docProp => docProp.BREQUIRE === true && !hasValue(task.docProps[docProp.SFORMATTED_ID])).length; + //Если доп. свойства загрузились и количество незаполненных = 0 - доп. свойства готовы, иначе не готовы + docProps.loaded && notFilled === 0 ? onDPReady(true) : onDPReady(false); + }, [docProps, onDPReady, task.docProps]); + + //Генерация содержимого + return ( + + + {task.nRn ? "Исправление события" : "Добавление события"} + + + + + {docProps.props.length > 0 ? : null} + + + + + + + + {docProps.props.length > 0 ? ( + + + + ) : null} + + ); +}; + +//Контроль свойств - Форма события +TaskForm.propTypes = { + task: PropTypes.object.isRequired, + taskType: PropTypes.string.isRequired, + onTaskChange: PropTypes.func.isRequired, + editable: PropTypes.bool.isRequired, + onEventNextNumbGet: PropTypes.func.isRequired, + onDPReady: PropTypes.func.isRequired +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { TaskForm }; diff --git a/app/panels/clnt_task_board/components/task_form_tab_executor.js b/app/panels/clnt_task_board/components/task_form_tab_executor.js new file mode 100644 index 0000000..9e681e6 --- /dev/null +++ b/app/panels/clnt_task_board/components/task_form_tab_executor.js @@ -0,0 +1,155 @@ +/* + Парус 8 - Панели мониторинга - УДП - Доски задач + Компонент: Вкладка информации об исполнителе +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента +import { Box, TextField } from "@mui/material"; //Интерфейсные компоненты +import { getInputProps } from "./task_form"; //Формирование кнопки доступа к разделу +import dayjs from "dayjs"; //Работа с датами +import customParseFormat from "dayjs/plugin/customParseFormat"; //Настройка пользовательского формата даты +import { COMMON_STYLES } from "../styles"; //Общие стили +import { useDictionary } from "../hooks/dict_hooks"; //Состояние открытия разделов + +//--------- +//Константы +//--------- + +//Стили +const STYLES = { + BOX_LEFT_ALIGN: { display: "flex", justifyContent: "flex-start" } +}; + +//------------------------------------ +//Вспомогательные функции и компоненты +//------------------------------------ + +//Подключение настройки пользовательского формата даты +dayjs.extend(customParseFormat); + +//----------- +//Тело модуля +//----------- + +//Вкладка информации об исполнителе +const TaskFormTabExecutor = ({ task, onFieldEdit }) => { + //Вспомогательные функции открытия раздела + const { handleClientPersonOpen } = useDictionary(); + + //При изменении сотрудника-инициатора + const handleInitClnpersonChange = () => + handleClientPersonOpen({ + sCode: task.sInitClnperson, + callBack: res => { + onFieldEdit({ + target: { + id: "sInitClnperson", + value: res.outParameters.out_CODE + } + }); + } + }); + + //Генерация содержимого + return ( + + + Планирование + + + + Инициатор + handleInitClnpersonChange(), task.isUpdate)} + > + + + + + Направить + + + + + + + + + + + ); +}; + +//Контроль свойств - Вкладка информации об исполнителе +TaskFormTabExecutor.propTypes = { + task: PropTypes.object.isRequired, + onFieldEdit: PropTypes.func.isRequired +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { TaskFormTabExecutor }; diff --git a/app/panels/clnt_task_board/components/task_form_tab_info.js b/app/panels/clnt_task_board/components/task_form_tab_info.js new file mode 100644 index 0000000..b97f7c8 --- /dev/null +++ b/app/panels/clnt_task_board/components/task_form_tab_info.js @@ -0,0 +1,192 @@ +/* + Парус 8 - Панели мониторинга - УДП - Доски задач + Компонент: Вкладка основной информации +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента +import { Box, TextField } from "@mui/material"; //Интерфейсные компоненты +import { getInputProps } from "./task_form"; //Формирование кнопки доступа к разделу +import { COMMON_STYLES } from "../styles"; //Общие стили +import { useDictionary } from "../hooks/dict_hooks"; //Состояние открытия разделов + +//--------- +//Константы +//--------- + +//Стили +const STYLES = { + BOX_FEW_COLUMNS: { display: "flex", flexWrap: "wrap", justifyContent: "space-between" } +}; + +//----------- +//Тело модуля +//----------- + +//Вкладка основной информации +const TaskFormTabInfo = ({ task, editable, onFieldEdit, onEventNextNumbGet }) => { + //Вспомогательные функции открытия раздела + const { handleClientPersonOpen, handleCatalogTreeOpen, handleClientClientsOpen } = useDictionary(); + + //При изменении каталога + const handleCrnChange = () => + handleCatalogTreeOpen({ + sUnitName: "ClientEvents", + sName: task.sCrn, + callBack: res => { + onFieldEdit({ + target: { + id: "sCrn", + value: res.outParameters.out_NAME + } + }); + } + }); + + //При изменении клиента-сотрудника + const handleClntClnpersonChange = () => + handleClientPersonOpen({ + sCode: task.sClntClnperson, + callBack: res => { + onFieldEdit({ + target: { + id: "sClntClnperson", + value: res.outParameters.out_CODE + } + }); + } + }); + + //При изменении клиента-организации + const handleClntClientsChange = () => + handleClientClientsOpen({ + sCode: task.sClntClients, + callBack: res => { + onFieldEdit({ + target: { + id: "sClntClients", + value: res.outParameters.out_CLIENT_CODE + } + }); + } + }); + + //Генерация содержимого + return ( + + + Событие + + + + + + + + + + + Клиент + handleClntClientsChange(), !task.sType)} + > + handleClntClnpersonChange(), !task.sType)} + > + + + ); +}; + +//Контроль свойств - Вкладка основной информации +TaskFormTabInfo.propTypes = { + task: PropTypes.object.isRequired, + editable: PropTypes.bool.isRequired, + onFieldEdit: PropTypes.func.isRequired, + onEventNextNumbGet: PropTypes.func.isRequired +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { TaskFormTabInfo }; diff --git a/app/panels/clnt_task_board/components/task_form_tab_props.js b/app/panels/clnt_task_board/components/task_form_tab_props.js new file mode 100644 index 0000000..dc89940 --- /dev/null +++ b/app/panels/clnt_task_board/components/task_form_tab_props.js @@ -0,0 +1,169 @@ +/* + Парус 8 - Панели мониторинга - УДП - Доски задач + Компонент: Вкладка информации со свойствами +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента +import { Box, TextField } from "@mui/material"; //Интерфейсные компоненты +import { getInputProps } from "./task_form"; //Формирование кнопки доступа к разделу +import dayjs from "dayjs"; //Работа с датами +import customParseFormat from "dayjs/plugin/customParseFormat"; //Настройка пользовательского формата даты +import { DP_DEFAULT_VALUE, DP_IN_VALUE, DP_RETURN_VALUE, validationError, formatSqlDate } from "../layouts"; //Дополнительная разметка и вёрстка клиентских элементов +import { COMMON_STYLES } from "../styles"; //Общие стили +import { useDictionary } from "../hooks/dict_hooks"; //Состояние открытия разделов + +//--------- +//Константы +//--------- + +//------------------------------------ +//Вспомогательные функции и компоненты +//------------------------------------ + +//Подключение настройки пользовательского формата даты +dayjs.extend(customParseFormat); + +//----------- +//Тело модуля +//----------- + +//Вкладка информации со свойствами +const TaskFormTabProps = ({ task, docProps, onPropEdit }) => { + //Вспомогательные функции открытия раздела + const { handleExtraDictionariesOpen, handleUnitOpen } = useDictionary(); + + //Выбор из словаря или дополнительного словаря + const handleDictOpen = async (docProp, curValue = null) => { + //Если способ выбора - словарь + docProp.NENTRY_TYPE === 1 + ? handleUnitOpen({ + sUnitCode: docProp.SUNITCODE, + sShowMethod: docProp.SMETHOD_CODE, + inputParameters: docProp.NPARAM_RN ? [{ name: docProp.SPARAM_IN_CODE, value: curValue }] : null, + callBack: res => { + onPropEdit(docProp.SFORMATTED_ID, res.outParameters[docProp.SPARAM_OUT_CODE]); + } + }) + : //Если способ выбора - доп. словарь + handleExtraDictionariesOpen({ + nRn: docProp.NEXTRA_DICT_RN, + sParamName: DP_IN_VALUE[docProp.NFORMAT], + paramValue: curValue, + callBack: res => { + onPropEdit(docProp.SFORMATTED_ID, res.outParameters[DP_RETURN_VALUE[docProp.NFORMAT]]); + } + }); + }; + + //Инициализация дополнительного свойства + const initPropValue = prop => { + //Считываем значение свойства из события + const value = task.docProps[prop.SFORMATTED_ID]; + //Если есть значение свойства + if (value) { + //Строка или число + if (prop.NFORMAT < 2) { + return prop.NNUM_PRECISION ? String(value).replace(".", ",") : value; + } + //Дата + if (prop.NFORMAT === 2) { + //Возвращаем значение исходя из подтипа даты + switch (prop.NDATA_SUBTYPE) { + //Дата без времени + case 0: + return dayjs(value).format("YYYY-MM-DD"); + //Дата и время без секунд + case 1: + return dayjs(value).format("YYYY-MM-DD HH:mm"); + //Дата и время с секундами + default: + return dayjs(value).format("YYYY-MM-DD HH:mm:ss"); + } + } + //Если это ничего из вышестоящего - время + return formatSqlDate(value); + } + //Если нет значения, но это изменение события + if (task.nRn) { + //Возвращаем пустоту + return ""; + } + //Если нет значения и это добавление события - возвращаем значение по умолчанию + return prop[DP_DEFAULT_VALUE[prop.NFORMAT]]; + }; + + //Генерация содержимого + return ( + + + {docProps.props.map((docProp, index) => { + return docProp.BSHOW_IN_GRID ? ( + onPropEdit(e.target.id, e.target.value)} + inputProps={ + (docProp.NFORMAT === 2 && docProp.NDATA_SUBTYPE === 2) || (docProp.NFORMAT === 3 && docProp.NDATA_SUBTYPE === 1) + ? { step: 1 } + : {} + } + InputProps={ + docProp.NENTRY_TYPE > 0 ? getInputProps(() => handleDictOpen(docProp, task.docProps[docProp.SFORMATTED_ID])) : null + } + InputLabelProps={ + docProp.NFORMAT < 2 + ? {} + : { + shrink: true + } + } + required={docProp.BREQUIRE} + disabled={docProp.BREADONLY} + /> + ) : null; + })} + + + ); +}; + +//Контроль свойств - Вкладка информации со свойствами +TaskFormTabProps.propTypes = { + task: PropTypes.object.isRequired, + docProps: PropTypes.object.isRequired, + onPropEdit: PropTypes.func.isRequired +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { TaskFormTabProps }; diff --git a/app/panels/clnt_task_board/filter.js b/app/panels/clnt_task_board/filter.js new file mode 100644 index 0000000..6591bb8 --- /dev/null +++ b/app/panels/clnt_task_board/filter.js @@ -0,0 +1,212 @@ +/* + Парус 8 - Панели мониторинга - УДП - Доски задач + Компонент: Фильтр отбора +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React, { useState } from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента +import { Chip, Stack, Icon, IconButton, Box, Menu, MenuItem, Typography } from "@mui/material"; //Интерфейсные компоненты +import { FilterDialog } from "./components/filter_dialog.js"; //Диалог фильтра +import { COMMON_STYLES } from "./styles"; //Общие стили + +//--------- +//Константы +//--------- + +//Стили +const STYLES = { + ICON_ORDERS: orders => { + return orders.length > 0 ? { color: "#1976d2" } : {}; + }, + MENU_ORDER: { + width: "260px" + }, + MENU_ITEM_ORDER: { + display: "flex", + justifyContent: "space-between" + }, + FILTERS_STACK: { + paddingBottom: "5px", + ...COMMON_STYLES.SCROLL + }, + STACK_FILTER: { maxWidth: "99vw" } +}; + +//-------------------------- +//Вспомогательные компоненты +//-------------------------- + +//Элемент меню сортировок +const SortMenuItem = ({ item, caption, orders, onOrderChanged }) => { + //Кнопка сортировки + const order = orders.find(order => order.name == item); + //Генерация содержимого + return ( + onOrderChanged(item)}> + {caption} + {order ? order.direction === "ASC" ? arrow_upward : arrow_downward : null} + + ); +}; + +//Контроль свойств компонента - Элемент меню сортировок +SortMenuItem.propTypes = { + item: PropTypes.string.isRequired, + caption: PropTypes.string.isRequired, + orders: PropTypes.array, + onOrderChanged: PropTypes.func.isRequired +}; + +//Меню сортировок +const SortMenu = ({ menuOrders, onOrdersMenuClose, orders, onOrderChanged }) => { + //Генерация содержимого + return ( + + + + + + ); +}; + +//Контроль свойств компонента - Меню сортировок +SortMenu.propTypes = { + menuOrders: PropTypes.object.isRequired, + onOrdersMenuClose: PropTypes.func.isRequired, + orders: PropTypes.array, + onOrderChanged: PropTypes.func.isRequired +}; + +//Элемент фильтра +const FilterItem = ({ caption, value, onClick }) => { + //При нажатии на элемент + const handleClick = () => (onClick ? onClick() : null); + + //Генерация содержимого + return ( + + {caption} + {value ? `:\u00A0${value}` : null} + + } + variant="outlined" + onClick={handleClick} + /> + ); +}; + +//Контроль свойств компонента - Элемент фильтра +FilterItem.propTypes = { + caption: PropTypes.string.isRequired, + value: PropTypes.any, + onClick: PropTypes.func +}; + +//--------------- +//Тело компонента +//--------------- + +//Фильтр отбора +const Filter = ({ + isFilterDialogOpen, + filter, + docLinks, + selectedDocLink, + onFilterChange, + onDocLinksLoad, + onFilterOpen, + onFilterClose, + onTasksReload, + orders, + onOrderChanged, + ...other +}) => { + //Состояние меню сортировки + const [menuOrders, setMenuOrders] = useState({ anchorMenuOrders: null, openOrders: false }); + + //При нажатии на открытие меню сортировки + const handleOrdersMenuButtonClick = event => { + setMenuOrders(pv => ({ ...pv, anchorMenuOrders: event.currentTarget, openOrders: true })); + }; + + //При закрытии меню + const handleOrdersMenuClose = () => { + setMenuOrders(pv => ({ ...pv, anchorMenuOrders: null, openOrders: false })); + }; + + //Генерация содержимого + return ( +
+ {isFilterDialogOpen ? ( + + ) : null} + + + + refresh + + + sort + + + filter_alt + + + {filter.sState ? : null} + {filter.sType ? : null} + {filter.sCrnName ? : null} + {filter.bSubcatalogs ? : null} + {filter.sSendPerson ? : null} + {filter.sSendDivision ? : null} + {filter.sSendUsrGrp ? ( + + ) : null} + {filter.sDocLink && selectedDocLink ? ( + + ) : null} + + + + +
+ ); +}; + +//Контроль свойств компонента - Фильтр отбора +Filter.propTypes = { + isFilterDialogOpen: PropTypes.bool.isRequired, + filter: PropTypes.object.isRequired, + docLinks: PropTypes.arrayOf(PropTypes.object), + selectedDocLink: PropTypes.object, + onFilterChange: PropTypes.func.isRequired, + onDocLinksLoad: PropTypes.func, + onFilterOpen: PropTypes.func.isRequired, + onFilterClose: PropTypes.func.isRequired, + onTasksReload: PropTypes.func.isRequired, + orders: PropTypes.array, + onOrderChanged: PropTypes.func.isRequired +}; + +//-------------------- +//Интерфейс компонента +//-------------------- + +export { Filter }; diff --git a/app/panels/clnt_task_board/hooks/dict_hooks.js b/app/panels/clnt_task_board/hooks/dict_hooks.js new file mode 100644 index 0000000..2b4ccfb --- /dev/null +++ b/app/panels/clnt_task_board/hooks/dict_hooks.js @@ -0,0 +1,255 @@ +/* + Парус 8 - Панели мониторинга - УДП - Доски задач + Пользовательские хуки: Хуки открытия разделов +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import { useContext, useCallback } from "react"; //Классы React +import { ApplicationСtx } from "../../../context/application"; //Контекст приложения + +//----------- +//Тело модуля +//----------- + +//Состояние открытия разделов +const useDictionary = () => { + //Подключение к контексту приложения + const { pOnlineShowDictionary } = useContext(ApplicationСtx); + + //Отображение раздела "Сотрудники" + const handleClientPersonOpen = useCallback( + async prms => { + pOnlineShowDictionary({ + unitCode: "ClientPersons", + showMethod: "main", + inputParameters: [{ name: "in_CODE", value: prms.sCode }], + callBack: res => { + res.success ? prms.callBack(res) : null; + } + }); + }, + [pOnlineShowDictionary] + ); + + //Отображение раздела "Клиенты" + const handleClientClientsOpen = useCallback( + async prms => { + pOnlineShowDictionary({ + unitCode: "ClientClients", + showMethod: "main", + inputParameters: [{ name: "in_CLIENT_CODE", value: prms.sCode }], + callBack: res => { + res.success ? prms.callBack(res) : null; + } + }); + }, + [pOnlineShowDictionary] + ); + + //Отображение раздела "Каталоги" + const handleCatalogTreeOpen = useCallback( + async prms => { + pOnlineShowDictionary({ + unitCode: "CatalogTree", + showMethod: "main", + inputParameters: [ + { name: "in_DOCNAME", value: prms.sUnitName }, + { name: "in_NAME", value: prms.sName } + ], + callBack: res => { + res.success ? prms.callBack(res) : null; + } + }); + }, + [pOnlineShowDictionary] + ); + + //Отображение раздела "Типы событий" + const handleEventTypesOpen = useCallback( + async prms => { + pOnlineShowDictionary({ + unitCode: "ClientEventTypes", + showMethod: "dictionary", + inputParameters: [{ name: "pos_eventtypecode", value: prms.sCode }], + callBack: res => { + res.success ? prms.callBack(res) : null; + } + }); + }, + [pOnlineShowDictionary] + ); + + //Отображение раздела "Контрагенты" + const handleAgnlistOpen = useCallback( + async prms => { + pOnlineShowDictionary({ + unitCode: "AGNLIST", + showMethod: "agents", + inputParameters: [{ name: "pos_agnmnemo", value: prms.sMnemo }], + callBack: res => { + res.success ? prms.callBack(res) : null; + } + }); + }, + [pOnlineShowDictionary] + ); + + //Отображение раздела "Штатные подразделения" + const handleInsDepartmentOpen = useCallback( + async prms => { + pOnlineShowDictionary({ + unitCode: "INS_DEPARTMENT", + inputParameters: [{ name: "in_CODE", value: prms.sCode }], + callBack: res => { + res.success ? prms.callBack(res) : null; + } + }); + }, + [pOnlineShowDictionary] + ); + + //Отображение раздела "Нештатные структуры" + const handleCostStaffGroupsOpen = useCallback( + async prms => { + pOnlineShowDictionary({ + unitCode: "CostStaffGroups", + inputParameters: [{ name: "in_CODE", value: prms.sCode }], + callBack: res => { + res.success ? prms.callBack(res) : null; + } + }); + }, + [pOnlineShowDictionary] + ); + + //Отображение раздела "Дополнительные словари" + const handleExtraDictionariesOpen = useCallback( + async prms => { + pOnlineShowDictionary({ + unitCode: "ExtraDictionaries", + showMethod: "values", + inputParameters: [ + { name: "pos_rn", value: prms.nRn }, + { name: prms.sParamName, value: prms.paramValue } + ], + callBack: res => { + res.success ? prms.callBack(res) : null; + } + }); + }, + [pOnlineShowDictionary] + ); + + //Отображение раздела "Маршруты событий (исполнители в точках)" + const handleEventRoutesPointExecutersOpen = useCallback( + async prms => { + pOnlineShowDictionary({ + unitCode: "EventRoutesPointExecuters", + showMethod: "executers", + inputParameters: prms.inputParameters, + callBack: res => { + res.success ? prms.callBack(res) : null; + } + }); + }, + [pOnlineShowDictionary] + ); + + //Отображение раздела "События" + const handleClientEventsOpen = useCallback( + async prms => { + pOnlineShowDictionary({ + unitCode: "ClientEvents", + inputParameters: [{ name: "in_Ident", value: prms.nIdent }] + }); + }, + [pOnlineShowDictionary] + ); + + //Отображение раздела "События (примечания)" + const handleClientEventsNotesOpen = useCallback( + async prms => { + pOnlineShowDictionary({ + unitCode: "ClientEventsNotes", + showMethod: "main", + inputParameters: [{ name: "in_PRN", value: prms.nPrn }] + }); + }, + [pOnlineShowDictionary] + ); + + //Отображение раздела "Присоединенные документы" + const handleFileLinksOpen = useCallback( + async prms => { + pOnlineShowDictionary({ + unitCode: "FileLinks", + showMethod: "main_link", + inputParameters: [ + { name: "in_PRN", value: prms.nPrn }, + { name: "in_UNITCODE", value: prms.sUnitCode } + ] + }); + }, + [pOnlineShowDictionary] + ); + + //Отображение раздела "Маршруты событий (точки перехода)" + const handleEventRoutesPointsPassessOpen = useCallback( + async prms => { + pOnlineShowDictionary({ + unitCode: "EventRoutesPointsPasses", + showMethod: "main_passes", + inputParameters: [ + { name: "in_ENVTYPE_CODE", value: prms.sEventType }, + { name: "in_ENVSTAT_CODE", value: prms.sEventStatus }, + { name: "in_POINT", value: prms.nPoint } + ], + callBack: res => { + res.success ? prms.callBack(res) : null; + } + }); + }, + [pOnlineShowDictionary] + ); + + //Универсальное отображение раздела + const handleUnitOpen = useCallback( + async prms => { + pOnlineShowDictionary({ + unitCode: prms.sUnitCode, + showMethod: prms.sShowMethod, + inputParameters: prms.inputParameters, + callBack: res => { + res.success ? prms.callBack(res) : null; + } + }); + }, + [pOnlineShowDictionary] + ); + + return { + handleClientPersonOpen, + handleClientClientsOpen, + handleCatalogTreeOpen, + handleEventTypesOpen, + handleAgnlistOpen, + handleInsDepartmentOpen, + handleCostStaffGroupsOpen, + handleExtraDictionariesOpen, + handleEventRoutesPointExecutersOpen, + handleClientEventsOpen, + handleClientEventsNotesOpen, + handleFileLinksOpen, + handleEventRoutesPointsPassessOpen, + handleUnitOpen + }; +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { useDictionary }; diff --git a/app/panels/clnt_task_board/hooks/filter_hooks.js b/app/panels/clnt_task_board/hooks/filter_hooks.js new file mode 100644 index 0000000..423375c --- /dev/null +++ b/app/panels/clnt_task_board/hooks/filter_hooks.js @@ -0,0 +1,122 @@ +/* + Парус 8 - Панели мониторинга - УДП - Доски задач + Пользовательские хуки: Хуки фильтра +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import { useState, useEffect, useCallback } from "react"; //Классы React +import { EVENT_STATES } from "../layouts"; //Перечисление состояний события +import { getLocalStorageValue } from "../layouts"; //Вспомогательные функции + +//-------------------------- +//Вспомогательные компоненты +//-------------------------- + +//Проверка возможности загрузки данных фильтра из локального хранилища +const isLocalStorageExists = () => { + return getLocalStorageValue("sType"); +}; + +//----------- +//Тело модуля +//----------- + +//Хук фильтра +//const useFilters = filterOpen => { +const useFilters = () => { + //Состояние фильтра + const [filters, setFilters] = useState({ + loaded: false, + isSetByUser: !isLocalStorageExists(), + values: { + sState: EVENT_STATES[1], + sType: "", + sCrnName: "", + sCrnRnList: "", + bSubcatalogs: false, + sSendPerson: "", + sSendDivision: "", + sSendUsrGrp: "", + sDocLink: "" + } + }); + + //Установить значение фильтра + const setFilterValues = useCallback((values, isSetByUser = true) => { + setFilters({ loaded: true, isSetByUser: isSetByUser, values: values }); + }, []); + + //Загрузка значений фильтра из локального хранилища браузера + const loadLocalStorageValues = useCallback(async () => { + //Загружаем значения по умолчанию + let values = { ...filters.values }; + //Обходим ключи объекта значений + for (let key in values) { + //Заполняем значениями из хранилища + switch (key) { + //Локальное хранилище не хранит булево, форматируем строку в булево + case "bSubcatalogs": + values[key] = getLocalStorageValue(key) === "true"; + break; + //Не переносим информацию о связанных записях + case "sDocLink": + break; + //Переносим все остальные значения + default: + values[key] = getLocalStorageValue(key, ""); + break; + } + } + //Устанавливаем значения фильтра + setFilterValues(values, false); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + //При изменении значений фильтра + const handleFiltersChange = useCallback( + filters => { + setFilterValues(filters); + }, + [setFilterValues] + ); + + //Сохранение при закрытии панели + useEffect(() => { + //Обработка события закрытия + const onBeforeUnload = () => { + //Обходим ключи фильтра + for (let key in filters.values) { + //Если это не связи - сохраняем значение в хранилище + key !== "sDocLink" ? localStorage.setItem(key, filters.values[key] ? filters.values[key] : "") : null; + } + }; + //Если данные были загружены и произошли изменения + if (filters.loaded && filters.isSetByUser) { + //Вешаем обработчик события закрытия + window.addEventListener("beforeunload", onBeforeUnload); + } + //Очищаем при размонтировании + return () => { + window.removeEventListener("beforeunload", onBeforeUnload); + }; + }, [filters.loaded, filters.isSetByUser, filters.values]); + + //При подключении к странице + useEffect(() => { + //Если требуется загрузить фильтр из локального хранилища + if (!filters.loaded && !filters.isSetByUser) { + loadLocalStorageValues(); + } + }, [filters.isSetByUser, filters.loaded, loadLocalStorageValues]); + + return [filters, handleFiltersChange]; +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { useFilters }; diff --git a/app/panels/clnt_task_board/hooks/hooks.js b/app/panels/clnt_task_board/hooks/hooks.js new file mode 100644 index 0000000..a7237d6 --- /dev/null +++ b/app/panels/clnt_task_board/hooks/hooks.js @@ -0,0 +1,243 @@ +/* + Парус 8 - Панели мониторинга - УДП - Доски задач + Пользовательские хуки: Хуки основных данных +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import { useState, useContext, useEffect, useCallback } from "react"; //Классы React +import { BackEndСtx } from "../../../context/backend"; //Контекст взаимодействия с сервером +import { getRandomColor, getLocalStorageValue } from "../layouts"; //Вспомогательные функции + +//----------- +//Тело модуля +//----------- + +//Хук дополнительных данных +const useExtraData = filtersType => { + //Состояние дополнительных данных + const [extraData, setExtraData] = useState({ + dataLoaded: false, + reload: false, + typeLoaded: "", + evRoutes: [], + evPoints: [], + noteTypes: [], + docLinks: [] + }); + + //Подключение к контексту взаимодействия с сервером + const { executeStored } = useContext(BackEndСtx); + + //Считывание учётных документов + const handleDocLinksLoad = useCallback( + async (type = filtersType) => { + //Считываем данные + const data = await executeStored({ + stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_DOCLINKS_GET", + args: { + SEVNTYPE_CODE: type + }, + isArray: name => name === "XDOCLINKS", + respArg: "COUT" + }); + //Возвращаем учётные документы + return [...(data?.XDOCLINKS || [])]; + }, + [executeStored, filtersType] + ); + + useEffect(() => { + //Загрузка дополнительных данных + const loadExtraData = async () => { + //Считываем данные + const data = await executeStored({ + stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_GET_INFO_BY_CODE", + args: { + SEVNTYPE_CODE: filtersType + }, + isArray: name => ["XEVROUTES", "XEVPOINTS", "XNOTETYPES"].includes(name), + respArg: "COUT" + }); + //Форматируем типы примечаний под нужный формат + let noteTypes = [...(data?.XNOTETYPES || [])].reduce((prev, cur) => [...prev, cur.SNAME], []); + //Считываем учётные документы + let docLinks = await handleDocLinksLoad(filtersType); + //Обновляем дополнительные данные + setExtraData({ + dataLoaded: true, + reload: false, + typeLoaded: filtersType, + evRoutes: [...(data?.XEVROUTES || [])], + evPoints: [...(data?.XEVPOINTS || [])], + noteTypes: [...noteTypes], + docLinks: [...docLinks] + }); + }; + + //Если указан тип событий и необходимо обновить + if (extraData.reload && filtersType) { + //Загружаем дополнительные данные + if (!extraData.typeLoaded || filtersType !== extraData.typeLoaded) { + loadExtraData(); + } + } + }, [executeStored, extraData.reload, extraData.typeLoaded, filtersType, handleDocLinksLoad]); + + return [extraData, setExtraData, handleDocLinksLoad]; +}; + +//Хук заливок пользовательских настроек +const useColorRules = () => { + //Собственное состояние + const [colorRules, setColorRules] = useState({ + loaded: false, + rules: [], + selectedColorRule: JSON.parse(getLocalStorageValue("settingsColorRule") || {}) + }); + + //Подключение к контексту взаимодействия с сервером + const { executeStored } = useContext(BackEndСtx); + + //При необходимости загрузки заливок + useEffect(() => { + //Считывание пользовательских настроек + let getColorRules = async () => { + //Считываем данные + const data = await executeStored({ + stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_DP_RULES_GET", + isArray: name => name === "XRULES", + respArg: "COUT" + }); + //Формируем массив правил заливки пользовательских настроек + let newColorRules = [...(data.XRULES || [])].reduce( + (prev, cur) => [ + ...prev, + { + id: prev.length, + SFIELD: cur.SFIELD, + SDP_NAME: cur.SDP_NAME, + SCOLOR: cur.SCOLOR, + STYPE: cur.STYPE, + fromValue: cur.NFROM ?? cur.SFROM ?? cur.DFROM, + toValue: cur.NTO ?? cur.STO ?? cur.DTO + } + ], + [] + ); + //Устанавливаем заливки пользовательских настроек + setColorRules(pv => ({ ...pv, loaded: true, rules: [...newColorRules] })); + }; + + if (!colorRules.loaded) getColorRules(); + }, [colorRules.loaded, executeStored]); + + //Сохранение при закрытии панели + useEffect(() => { + //Обработка события закрытия + const onBeforeUnload = () => { + localStorage.setItem("settingsColorRule", JSON.stringify(colorRules.selectedColorRule)); + }; + //Вешаем обработчик события закрытия + window.addEventListener("beforeunload", onBeforeUnload); + //Очищаем при размонтировании + return () => { + window.removeEventListener("beforeunload", onBeforeUnload); + }; + }, [colorRules.selectedColorRule]); + + return [colorRules, setColorRules]; +}; + +//Хук статусов событий +const useStatuses = filterType => { + //Собственное состояние статусов + const [statuses, setStatuses] = useState([]); + + //Состояние статусов + const [statusesState, setStatusesState] = useState({ + sorted: false, + reload: true, + attr: getLocalStorageValue("statusesSortAttr", "SEVNSTAT_NAME"), + direction: getLocalStorageValue("statusesSortDirection", "asc") + }); + + //Подключение к контексту взаимодействия с сервером + const { executeStored } = useContext(BackEndСtx); + + //При необходимости сортировки статусов + useEffect(() => { + //Сортируем статусы + const sortStatuses = unsortedStatuses => { + //Инициализируем поле сортировки и порядок сортировки + const attr = statusesState.attr; + const direction = statusesState.direction; + //Сортируем + let sortedStatuses = unsortedStatuses.sort((a, b) => + direction === "asc" ? a[attr].localeCompare(b[attr]) : b[attr].localeCompare(a[attr]) + ); + //Возвращаем + return sortedStatuses; + }; + //Загружаем и сортируем статусы + const loadAndSortStatuses = async filterType => { + //Инициализируем статусы + let newStatuses = []; + //Если требуется перезагрузка + if (statusesState.reload) { + const loadedStatuses = await executeStored({ + stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVNSTATS_LOAD", + args: { + SCLNEVNTYPES: filterType + }, + isArray: name => name === "XSTATUS", + respArg: "COUT" + }); + //Загружаем статусы и инициализируем цвета + newStatuses = [...(loadedStatuses?.XSTATUS || [])].reduce( + (prev, cur) => [...prev, { ...cur, color: getRandomColor(prev.length + 1) }], + [] + ); + } else { + //Загружаем из состояния + newStatuses = [...statuses]; + } + //Сортируем, если требуется + newStatuses = !statusesState.sorted ? sortStatuses(newStatuses) : newStatuses; + //Обновляем состояние статусов + setStatuses([...newStatuses]); + //Обновляем информацию о состоянии статусов + setStatusesState(pv => ({ ...pv, sorted: true, reload: false })); + }; + //При необходимости изменения сортировки + if (filterType && (statusesState.reload || !statusesState.sorted)) { + //Считываем старые статусы или загружаем новые + loadAndSortStatuses(filterType); + } + }, [executeStored, filterType, statuses, statusesState.attr, statusesState.direction, statusesState.reload, statusesState.sorted]); + + //Сохранение при закрытии панели + useEffect(() => { + //Обработка события закрытия + const onBeforeUnload = () => { + localStorage.setItem("statusesSortAttr", statusesState.attr); + localStorage.setItem("statusesSortDirection", statusesState.direction); + }; + //Вешаем обработчик события закрытия + window.addEventListener("beforeunload", onBeforeUnload); + //Очищаем при размонтировании + return () => { + window.removeEventListener("beforeunload", onBeforeUnload); + }; + }, [statusesState.attr, statusesState.direction]); + + return [statuses, statusesState, setStatuses, setStatusesState]; +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { useExtraData, useColorRules, useStatuses }; diff --git a/app/panels/clnt_task_board/hooks/task_dialog_hooks.js b/app/panels/clnt_task_board/hooks/task_dialog_hooks.js new file mode 100644 index 0000000..55b45a7 --- /dev/null +++ b/app/panels/clnt_task_board/hooks/task_dialog_hooks.js @@ -0,0 +1,191 @@ +/* + Парус 8 - Панели мониторинга - УДП - Доски задач + Пользовательские хуки: Хуки диалога события +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import { useState, useContext, useEffect } from "react"; //Классы React +import { BackEndСtx } from "../../../context/backend"; //Контекст взаимодействия с сервером + +//----------- +//Тело модуля +//----------- + +//Хук для события +const useClientEvent = (taskRn, taskType = "", taskStatus = "") => { + //Собственное состояние + const [task, setTask] = useState({ + init: true, + nRn: taskRn, + sCrn: "", + sPrefix: "", + sNumber: "", + sType: taskType, + sStatus: taskStatus, + sDescription: "", + sClntClients: "", + sClntClnperson: "", + dStartDate: "", + sInitClnperson: "", + sInitUser: "", + sInitReason: "", + sToCompany: "", + sToDepartment: "", + sToClnpost: "", + sToClnpsdep: "", + sToClnperson: "", + sToFcstaffgrp: "", + sToUser: "", + sToUsergrp: "", + sCurrentUser: "", + isUpdate: false, + insertDisabled: true, + updateDisabled: true, + docProps: {} + }); + + //Подключение к контексту взаимодействия с сервером + const { executeStored } = useContext(BackEndСtx); + + //При инициализации события + useEffect(() => { + //Если это инициализация + if (task.init) { + //Если указан рег. номер события + if (taskRn) { + //Считывание параметров события + const readEvent = async () => { + //Считываем информацию о событии по рег. номеру + const data = await executeStored({ + stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_GET", + args: { + NCLNEVENTS: task.nRn + }, + respArg: "COUT" + }); + //Фильтруем доп. свойства + let docProps = Object.keys(data.XEVENT) + .filter(key => key.includes("DP_")) + .reduce((prev, key) => ({ ...prev, [key]: data.XEVENT[key] }), {}); + //Устанавливаем информацию о событии + setTask(pv => ({ + ...pv, + sCrn: data.XEVENT.SCRN, + sPrefix: data.XEVENT.SPREF, + sNumber: data.XEVENT.SNUMB, + sType: data.XEVENT.STYPE, + sStatus: data.XEVENT.SSTATUS, + sDescription: data.XEVENT.SDESCRIPTION, + sClntClients: data.XEVENT.SCLIENT_CLIENT, + sClntClnperson: data.XEVENT.SCLIENT_PERSON, + dPlanDate: data.XEVENT.SPLAN_DATE, + sInitClnperson: data.XEVENT.SINIT_PERSON, + sInitUser: data.XEVENT.SINIT_AUTHID, + sInitReason: data.XEVENT.SREASON, + sToCompany: data.XEVENT.SSEND_CLIENT, + sToDepartment: data.XEVENT.SSEND_DIVISION, + sToClnpost: data.XEVENT.SSEND_POST, + sToClnpsdep: data.XEVENT.SSEND_PERFORM, + sToClnperson: data.XEVENT.SSEND_PERSON, + sToFcstaffgrp: data.XEVENT.SSEND_STAFFGRP, + sToUser: data.XEVENT.SSEND_USER_NAME, + sToUsergrp: data.XEVENT.SSEND_USER_GROUP, + sCurrentUser: data.XEVENT.SINIT_AUTHID, + isUpdate: true, + init: false, + docProps: docProps + })); + }; + //Инициализация параметров события + readEvent(); + } else { + //Считывание изначальных параметров события + const initEvent = async () => { + //Инициализируем параметры события + const data = await executeStored({ + stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_INIT", + args: { + SEVENT_TYPE: task.sType + } + }); + //Если есть данные + if (data) { + //Устанавливаем данные по событию + setTask(pv => ({ + ...pv, + sPrefix: data.SPREF, + sNumber: data.SNUMB, + sCurrentUser: data.SINIT_AUTHNAME, + sInitClnperson: data.SINIT_PERSON, + sInitUser: !data.SINIT_PERSON ? data.SINIT_AUTHNAME : "", + init: false + })); + } + }; + //Инициализация изначальных параметров события + initEvent(); + } + } + if (!task.init) { + setTask(pv => ({ ...pv, sInitUser: !task.sInitClnperson ? task.sCurrentUser : "" })); + } + }, [executeStored, task.init, task.nRn, task.sType, task.sCurrentUser, task.sInitClnperson, taskRn]); + + //Проверка доступности действия + useEffect(() => { + setTask(pv => ({ + ...pv, + insertDisabled: + !task.sCrn || + !task.sPrefix || + !task.sNumber || + !task.sType || + !task.sStatus || + !task.sDescription || + (!task.sInitClnperson && !task.sInitUser), + updateDisabled: !task.sDescription + })); + }, [task.sCrn, task.sDescription, task.sInitClnperson, task.sInitUser, task.sNumber, task.sPrefix, task.sStatus, task.sType]); + + return [task, setTask]; +}; + +//Хук для получения свойств раздела "События" +const useDocsProps = taskType => { + //Собственное состояние + const [docProps, setDocsProps] = useState({ loaded: false, props: [] }); + + //Подключение к контексту взаимодействия с сервером + const { executeStored } = useContext(BackEndСtx); + + useEffect(() => { + //Загрузка доп. свойств + let getDocsProps = async () => { + //Считываема доп. свойства по типу события + const data = await executeStored({ + stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_PROPS_GET", + args: { SEVNTYPE_CODE: taskType }, + isArray: name => name === "XPROPS", + respArg: "COUT" + }); + //Устанавливаем доп. свойства + setDocsProps({ loaded: true, props: [...(data?.XPROPS || [])] }); + }; + //Если доп. свойства не загружены + if (!docProps.loaded) { + //Загружаем доп. свойства + getDocsProps(); + } + }, [docProps.loaded, executeStored, taskType]); + + return [docProps]; +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { useClientEvent, useDocsProps }; diff --git a/app/panels/clnt_task_board/hooks/tasks_hooks.js b/app/panels/clnt_task_board/hooks/tasks_hooks.js new file mode 100644 index 0000000..99a3fa8 --- /dev/null +++ b/app/panels/clnt_task_board/hooks/tasks_hooks.js @@ -0,0 +1,431 @@ +/* + Парус 8 - Панели мониторинга - УДП - Доски задач + Пользовательские хуки: Хуки событий +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import { useState, useContext, useEffect, useCallback } from "react"; //Классы React +import { BackEndСtx } from "../../../context/backend"; //Контекст взаимодействия с сервером +import { object2Base64XML } from "../../../core/utils"; //Вспомогательные функции +import { convertFilterValuesToArray } from "../layouts"; //Вспомогательные функции +import { useDictionary } from "./dict_hooks"; //Состояние открытия разделов + +//----------- +//Тело модуля +//----------- + +//Хук обработки перехода события +const useTasksFunctions = () => { + //Состояние открытия раздела + const { handleEventRoutesPointExecutersOpen, handleEventRoutesPointsPassessOpen } = useDictionary(); + + //Подключение к контексту взаимодействия с сервером + const { executeStored } = useContext(BackEndСtx); + + //Выполнение направления события + const handleSendExec = useCallback( + //Выполняем финальное перенаправление события + async ({ mainArgs, onReload = null }) => { + await executeStored({ + stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_SEND", + args: { ...mainArgs } + }); + //Если требуется перезагрузить данные + onReload ? onReload() : null; + }, + [executeStored] + ); + + //При направлении события + const handleSend = useCallback( + async ({ mainArgs, onReload = null, onNoteOpen = null }) => { + //Если требуется добавить примечание + if (onNoteOpen) { + //Открываем примечание с коллбэком на направление события + onNoteOpen(async note => { + //Выполняем изменение статуса + handleSendExec({ mainArgs: { ...mainArgs, SNOTE_HEADER: note.header, SNOTE: note.text }, onReload }); + }); + } else { + //Выполняем изменение статуса + handleSendExec({ mainArgs, onReload }); + } + }, + [handleSendExec] + ); + + //По нажатию действия "Направить" + const handleTaskSend = useCallback( + async ({ nEvent, onReload = null, onNoteOpen = null }) => { + //Выполняем инициализацию параметров + const firstStep = await executeStored({ + stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_SEND", + args: { + NSTEP: 1, + NEVENT: nEvent + } + }); + if (firstStep) { + //Открываем раздел "Маршруты событий (исполнители в точках)" для выбора исполнителя + handleEventRoutesPointExecutersOpen({ + inputParameters: [ + { name: "in_IDENT", value: firstStep.NIDENT }, + { name: "in_EVENT", value: nEvent }, + { name: "in_PERSON_CODE", value: firstStep.SSEND_PERSON }, + { name: "in_USER_NAME", value: firstStep.SSEND_USER_NAME }, + { name: "in_EVENT_TYPE", value: firstStep.SEVENT_TYPE }, + { name: "in_EVENT_STAT", value: firstStep.SEVENT_STAT }, + { name: "in_INIT_PERSON", value: firstStep.SINIT_PERSON }, + { name: "in_INIT_AUTHNAME", value: firstStep.SINIT_AUTHNAME }, + { name: "in_CLIENT_CLIENT", value: firstStep.SCLIENT_CLIENT }, + { name: "in_CLIENT_PERSON", value: firstStep.SCLIENT_PERSON } + ], + callBack: sendPrms => { + //Собираем основные параметры направления события + const mainArgs = { + NIDENT: firstStep.NIDENT, + NSTEP: 2, + NEVENT: nEvent, + SSEND_CLIENT: sendPrms.outParameters.out_CLIENT_CODE, + SSEND_DIVISION: sendPrms.outParameters.out_DIVISION_CODE, + SSEND_POST: sendPrms.outParameters.out_POST_CODE, + SSEND_PERFORM: sendPrms.outParameters.out_POST_IN_DIV_CODE, + SSEND_PERSON: sendPrms.outParameters.out_PERSON_CODE, + SSEND_STAFFGRP: sendPrms.outParameters.out_STAFFGRP_CODE, + SSEND_USER_GROUP: sendPrms.outParameters.out_USER_GROUP_CODE, + SSEND_USER_NAME: sendPrms.outParameters.out_USER_NAME, + NSEND_PREDEFINED_EXEC: sendPrms.outParameters.out_PREDEFINED_EXEC, + NSEND_PREDEFINED_PROC: sendPrms.outParameters.out_PREDEFINED_PROC + }; + //Перенаправляем событие + handleSend({ nEvent, mainArgs, onReload, onNoteOpen }); + } + }); + } + }, + [executeStored, handleEventRoutesPointExecutersOpen, handleSend] + ); + + //Выполнение изменения статуса события + const handleStateChangeExec = useCallback( + async ({ mainArgs, onReload = null }) => { + await executeStored({ + stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_STATE_CHANGE", + args: { ...mainArgs } + }); + //Если требуется перезагрузить данные + onReload ? onReload() : null; + }, + [executeStored] + ); + + //При изменении статуса события + const handleStateChange = useCallback( + async ({ mainArgs, onReload = null, onNoteOpen = null }) => { + //Если необходимо добавить примечание + if (onNoteOpen) { + //Открываем примечание с коллбэком на изменение статуса + onNoteOpen(async note => { + //Выполняем изменение статуса + handleStateChangeExec({ mainArgs: { ...mainArgs, SNOTE_HEADER: note.header, SNOTE: note.text }, onReload }); + }); + } else { + //Выполняем изменение статуса + handleStateChangeExec({ mainArgs, onReload }); + } + }, + [handleStateChangeExec] + ); + + //При выборе исполнителя + const handleExecuterSelect = useCallback( + async ({ nEvent, pointInfo, onReload = null, onNoteOpen = null }) => { + //Если требуется выбрать получателя + if (pointInfo.NSELECT_EXEC === 1) { + //Открываем раздел "Маршруты событий (исполнители в точках)" для выбора исполнителя + handleEventRoutesPointExecutersOpen({ + inputParameters: [ + { name: "in_IDENT", value: pointInfo.NIDENT }, + { name: "in_EVENT", value: nEvent }, + { name: "in_EVENT_TYPE", value: pointInfo.SEVENT_TYPE }, + { name: "in_EVENT_STAT", value: pointInfo.SEVENT_STAT }, + { name: "in_INIT_PERSON", value: pointInfo.SINIT_PERSON }, + { name: "in_INIT_AUTHNAME", value: pointInfo.SINIT_AUTHNAME }, + { name: "in_CLIENT_CLIENT", value: pointInfo.SCLIENT_CLIENT }, + { name: "in_CLIENT_PERSON", value: pointInfo.SCLIENT_PERSON } + ], + callBack: sendPrms => { + const mainArgs = { + NIDENT: pointInfo.NIDENT, + NSTEP: 3, + NEVENT: nEvent, + SEVENT_STAT: pointInfo.SEVENT_STAT, + SSEND_CLIENT: sendPrms.outParameters.out_CLIENT_CODE, + SSEND_DIVISION: sendPrms.outParameters.out_DIVISION_CODE, + SSEND_POST: sendPrms.outParameters.out_POST_CODE, + SSEND_PERFORM: sendPrms.outParameters.out_POST_IN_DIV_CODE, + SSEND_PERSON: sendPrms.outParameters.out_PERSON_CODE, + SSEND_STAFFGRP: sendPrms.outParameters.out_STAFFGRP_CODE, + SSEND_USER_GROUP: sendPrms.outParameters.out_USER_GROUP_CODE, + SSEND_USER_NAME: sendPrms.outParameters.out_USER_NAME, + NSEND_PREDEFINED_EXEC: sendPrms.outParameters.out_PREDEFINED_EXEC, + NSEND_PREDEFINED_PROC: sendPrms.outParameters.out_PREDEFINED_PROC + }; + //Выполняем изменение статуса + handleStateChange({ mainArgs, onReload, onNoteOpen }); + } + }); + } else { + //Общие аргументы + const mainArgs = { + NIDENT: pointInfo.NIDENT, + NSTEP: 3, + NEVENT: nEvent, + SEVENT_STAT: pointInfo.SEVENT_STAT + }; + //Выполняем изменение статуса + handleStateChange({ mainArgs, onReload, onNoteOpen }); + } + }, + [handleEventRoutesPointExecutersOpen, handleStateChange] + ); + + //При выполнении второго шага + const handleMakeSecondStep = useCallback( + async ({ nIdent, nPass }) => { + //Выполняем переход на следующий шаг + const secondStep = await executeStored({ + stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_STATE_CHANGE", + args: { + NIDENT: nIdent, + NSTEP: 2, + NPASS: nPass + } + }); + //Возвращаем параметры выполнения + return secondStep; + }, + [executeStored] + ); + + //При выборе следующей точки события + const handleNextPointSelect = useCallback( + ({ nEvent, pointInfo, onReload = null, onNoteOpen = null }) => { + //Открываем раздел "Маршруты событий (точки перехода)" для выбора следующей точки + handleEventRoutesPointsPassessOpen({ + sEventType: pointInfo.SEVENT_TYPE, + sEventStatus: pointInfo.SEVENT_STAT, + nPoint: pointInfo.NPOINT, + callBack: async point => { + //Выполняем второй шаг + let secondStep = await handleMakeSecondStep({ nIdent: pointInfo.NIDENT, nPass: point.outParameters.out_RN }); + //Выполняем выбор исполнителя + handleExecuterSelect({ + nEvent, + pointInfo: { ...pointInfo, SEVENT_STAT: point.outParameters.out_NEXT_POINT, NSELECT_EXEC: secondStep.NSELECT_EXEC }, + onReload, + onNoteOpen + }); + } + }); + }, + [handleEventRoutesPointsPassessOpen, handleMakeSecondStep, handleExecuterSelect] + ); + + //По нажатию действия "Перейти" + const handleTaskStateChange = useCallback( + async ({ nEvent, sNextStat = null, onReload = null, onNoteOpen = null }) => { + //Выполняем инициализацию параметров + const eventInfo = await executeStored({ + stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_STATE_CHANGE", + args: { + NSTEP: 1, + NEVENT: nEvent, + SNEXT_STAT: sNextStat + } + }); + //Если информация о события проинициализирована + if (eventInfo) { + //Если следующий статус неопределен + if (!sNextStat) { + //Выполнение перехода с выбором точки + handleNextPointSelect({ + nEvent, + pointInfo: eventInfo, + onReload, + onNoteOpen + }); + } else { + //Выполняем перехода без выбора точки + handleExecuterSelect({ + nEvent, + pointInfo: eventInfo, + onReload, + onNoteOpen + }); + } + } + }, + [executeStored, handleExecuterSelect, handleNextPointSelect] + ); + + return { handleTaskStateChange, handleTaskSend }; +}; + +//Хук получения событий +const useTasks = (filterValues, ordersValues) => { + //Состояние событий + const [tasks, setTasks] = useState({ + loaded: false, + rows: [], + reload: false, + accountsReload: false, + loadedAccounts: [] + }); + + //Состояние вспомогательных функций событий + const { handleTaskStateChange } = useTasksFunctions(); + + //Подключение к контексту взаимодействия с сервером + const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx); + + //Инициализация параметров события + const initTask = (id, task, avatar = null) => { + //Фильтруем доп. свойства + let newDocProps = Object.keys(task) + .filter(key => key.includes("DP_")) + .reduce((prev, key) => ({ ...prev, [key]: task[key] }), {}); + //Возвращаем структуру события + return { + id: id, + avatar: avatar, + name: task.SPREF_NUMB, + nRn: task.NRN, + sCrn: "", + sPrefix: task.SEVPREF, + sNumber: task.SEVNUMB, + sType: task.SEVTYPE_CODE, + sStatus: task.SEVSTAT_NAME, + sDescription: task.SEVDESCR, + sClntClients: "", + sClntClnperson: "", + dchange_date: task.DCHANGE_DATE, + dStartDate: task.DREG_DATE, + dExpireDate: task.DEXPIRE_DATE, + dPlanDate: task.DPLAN_DATE, + sInitClnperson: task.SINIT_PERSON, + sInitUser: "", + sInitReason: "", + sToCompany: "", + sToDepartment: task.SSEND_DIVISION, + sToClnpost: "", + sToClnpsdep: "", + sToClnperson: task.SSEND_PERSON, + sToFcstaffgrp: "", + sToUser: "", + sToUsergrp: task.SSEND_USRGRP, + sSender: task.SSENDER, + sCurrentUser: "", + sLinkedUnit: task.SLINKED_UNIT, + nLinkedRn: task.NLINKED_RN, + docProps: newDocProps + }; + }; + + //Взаимодействие с событием (через перенос) + const onDragEnd = useCallback( + ({ path, eventPoints, openNoteDialog, destCode }) => { + //Определяем нужные параметры + const { source, destination } = path; + //Если путь не указан + if (!destination) { + return; + } + //Если происходит изменение статуса + if (destination.droppableId !== source.droppableId) { + //Конвертим ID переносимого события + let nDraggableTaskId = parseInt(path.draggableId); + //Считываем строку, у которой изменяется статус + let task = tasks.rows.find(r => r.id === nDraggableTaskId); + //Изменяем статус у события + task.statusId = parseInt(path.destination.droppableId); + //Получение настройки точки назначения + const pointSettings = eventPoints.find(eventPoint => eventPoint.SEVPOINT === destCode); + //Изменяем статус события с добавлением примечания + handleTaskStateChange({ + nEvent: task.nRn, + sNextStat: destCode, + onReload: () => setTasks(pv => ({ ...pv, reload: true, accountsReload: true })), + onNoteOpen: pointSettings.ADDNOTE_ONCHST ? openNoteDialog : null + }); + } + }, + [handleTaskStateChange, tasks.rows] + ); + + //При необходимости перезагрузки данных + useEffect(() => { + //Считывание данных с учетом фильтрации + let getTasks = async () => { + const data = await executeStored({ + stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_LOAD", + args: { + CFILTERS: { + VALUE: object2Base64XML(convertFilterValuesToArray(filterValues), { arrayNodeName: "filters" }), + SDATA_TYPE: SERV_DATA_TYPE_CLOB + }, + CORDERS: { VALUE: object2Base64XML(ordersValues, { arrayNodeName: "orders" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB }, + NINCLUDE_ACCOUNTS: tasks.accountsReload ? 1 : 0 + }, + isArray: name => name === "XAGENTS", + respArg: "COUT" + }); + //Считываем информацию о событиях + let events = data.XCLNEVENTS.XDATA.XDATA_GRID; + //Считываем иноформацию о контрагентах + let accounts = tasks.accountsReload ? [...(data.XAGENTS_WITH_IMG.XAGENTS || [])] : tasks.loadedAccounts; + //Инициализируем события + let newRows = []; + //Если есть события + if (events.rows) { + //Формируем структуру событий + newRows = [...(events.rows || [])].reduce( + (prev, cur) => [...prev, initTask(prev.length, cur, accounts.find(agent => agent.SAGNABBR === cur.SSENDER)?.BIMAGE)], + [] + ); + } + //Возвращаем информацию + return { rows: [...newRows], loadedAccounts: accounts }; + }; + //Считывание данных + let getData = async () => { + //Считываем информацию о задачах + let eventTasks = await getTasks(); + //Загружаем данные + setTasks(pv => ({ + ...pv, + loaded: true, + rows: eventTasks.rows, + loadedAccounts: eventTasks.loadedAccounts, + reload: false, + accountsReload: false + })); + }; + //Если необходимо загрузить данные и указан тип событий и загружены все необходимые вспомогательные данные + if (tasks.reload) { + //Загружаем данные + getData(); + } + }, [SERV_DATA_TYPE_CLOB, executeStored, filterValues, ordersValues, tasks.accountsReload, tasks.loadedAccounts, tasks.reload]); + + return [tasks, setTasks, onDragEnd]; +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { useTasksFunctions, useTasks }; diff --git a/app/panels/clnt_task_board/index.js b/app/panels/clnt_task_board/index.js new file mode 100644 index 0000000..0324d4a --- /dev/null +++ b/app/panels/clnt_task_board/index.js @@ -0,0 +1,16 @@ +/* + Парус 8 - Панели мониторинга - УДП - Доски задач + Панель мониторинга: Точка входа +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import { ClntTaskBoard } from "./clnt_task_board"; //Корневая панель выполнения работ + +//---------------- +//Интерфейс модуля +//---------------- + +export const RootClass = ClntTaskBoard; diff --git a/app/panels/clnt_task_board/layouts.js b/app/panels/clnt_task_board/layouts.js new file mode 100644 index 0000000..f52b9b9 --- /dev/null +++ b/app/panels/clnt_task_board/layouts.js @@ -0,0 +1,294 @@ +/* + Парус 8 - Панели мониторинга - УДП - Доски задач + Дополнительная разметка и вёрстка клиентских элементов +*/ + +//--------- +//Константы +//--------- + +//Перечисление "Состояние события" +export const EVENT_STATES = Object.freeze({ 0: "Все", 1: "Не аннулированные", 2: "Аннулированные" }); + +//Допустимые значение поля сортировки +export const sortAttrs = [ + { id: "SEVNSTAT_CODE", descr: "Мнемокод" }, + { id: "SEVNSTAT_NAME", descr: "Наименование" }, + { id: "SEVPOINT_DESCR", descr: "Описание точки маршрута" } +]; + +//Допустимые значения направления сортировки +export const sortDest = []; +sortDest[-1] = "desc"; +sortDest[1] = "asc"; + +//Цвета статусов +export const COLORS = [ + "mediumSlateBlue", + "lightSalmon", + "fireBrick", + "orange", + "gold", + "limeGreen", + "yellowGreen", + "mediumAquaMarine", + "paleTurquoise", + "steelBlue", + "skyBlue", + "tan" +]; + +//Перечисление "Цвет задачи" +export const TASK_COLORS = Object.freeze({ EXPIRED: "#ff0000", EXPIRES_SOON: "#ffdf00", LINKED: "#1e90ff" }); + +//Перечисление Доп. свойства "Значение по умолчанию" +export const DP_DEFAULT_VALUE = Object.freeze({ 0: "SDEFAULT_STR", 1: "NDEFAULT_NUM", 2: "DDEFAULT_DATE", 3: "NDEFAULT_NUM" }); +//Перечисление Доп. свойства "Префикс формата данных" +export const DP_TYPE_PREFIX = Object.freeze({ 0: "S", 1: "N", 2: "D", 3: "N" }); +//Перечисление Доп. свойства "Входящее значение дополнительного словаря" +export const DP_IN_VALUE = Object.freeze({ 0: "pos_str_value", 1: "pos_num_value", 2: "pos_date_value", 3: "pos_num_value" }); +//Перечисление Доп. свойства "Исходящее значение дополнительного словаря" +export const DP_RETURN_VALUE = Object.freeze({ 0: "str_value", 1: "num_value", 2: "date_value", 3: "num_value" }); + +//----------- +//Тело модуля +//----------- + +//Формирование массива из 0, 1 и более элементов +export const makeArray = arr => { + return arr ? (arr.length ? arr : [arr]) : []; +}; + +//Конвертация формата HEX в формат RGB +const convertHexToRGB = hex => { + let r = parseInt(hex.slice(1, 3), 16); + let g = parseInt(hex.slice(3, 5), 16); + let b = parseInt(hex.slice(5, 7), 16); + let a = 0.5; + r = Math.round((a * (r / 255) + a * (255 / 255)) * 255); + g = Math.round((a * (g / 255) + a * (255 / 255)) * 255); + b = Math.round((a * (b / 255) + a * (255 / 255)) * 255); + return "rgb(" + r + ", " + g + ", " + b + ")"; +}; + +//Считывание заливки события по условию +export const getTaskBgColorByRule = (task, colorRule) => { + //Инициализируем значения + let ruleCode = ""; + //Исходя из типа определяем наименование + switch (colorRule.STYPE) { + case "number": + ruleCode = `N${colorRule.SFIELD}`; + break; + case "date": + ruleCode = `D${colorRule.SFIELD}`; + break; + default: + ruleCode = `S${colorRule.SFIELD}`; + break; + } + //Определяем цвет заливки + let bgColor = ruleCode && task.docProps[ruleCode] == colorRule.fromValue ? convertHexToRGB(colorRule.SCOLOR) : null; + //Возвращаем цвет заливки + return bgColor; +}; + +//Индикация истечения срока отработки события +export const getTaskExpiredColor = task => { + //Определяем текущую дату + let sysDate = new Date(); + //Определяем дату истечения срока события + let expireDate = task.dExpireDate ? new Date(task.dExpireDate) : null; + //Если дата истечения срока определена + if (expireDate) { + //Определяем разницу между датами + let daysDiff = ((expireDate.getTime() - sysDate.getTime()) / (1000 * 60 * 60 * 24)).toFixed(2); + //Если разница меньше 0 - срок истечен + if (daysDiff < 0) return TASK_COLORS.EXPIRED; + //Если разница меньше 4 - скоро истечет + if (daysDiff < 4) return TASK_COLORS.EXPIRES_SOON; + } + return null; +}; + +//Цвет из hsl формата в rgba формат +const convertHslToRgba = (h, s, l) => { + s /= 100; + l /= 100; + const k = n => (n + h / 30) % 12; + const a = s * Math.min(l, 1 - l); + const f = n => l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1))); + return `rgba(${Math.floor(255 * f(0))},${Math.floor(255 * f(8))},${Math.floor(255 * f(4))},0.3)`; +}; + +//Формирование случайного цвета +export const getRandomColor = index => { + const hue = index * 137.508; + return convertHslToRgba(hue, 50, 70); +}; + +//Формат дополнительного свойства типа число (длина, точность) +const formatRegExpNum = (width, precision) => + new RegExp("^(\\d{1," + (width - precision) + "}" + (precision > 0 ? "((\\.|,)\\d{1," + precision + "})?" : "") + ")?$"); + +//Формат дополнительного свойства типа строка (длина) +const formatRegExpStr = length => new RegExp("^.{0," + length + "}$"); + +//Проверка валидности числа +const isValidNum = (width, precision, value) => { + return formatRegExpNum(width, precision).test(value); +}; + +//Проверка валидности строки +const isValidStr = (length, value) => { + return formatRegExpStr(length).test(value); +}; + +//Признак ошибки валидации +export const validationError = (value = "", format, numWidth, numPrecision, strLength) => { + //Исходим от формата + switch (format) { + //Проверка строки + case 0: + return isValidStr(strLength, value); + //Проверка числа + case 1: + return isValidNum(numWidth, numPrecision, value); + //Остальное не проверяем + default: + return true; + } +}; + +//Конвертация времени в привычный формат +export const formatSqlDate = timeStamp => { + //Если есть разделитель + if (timeStamp.indexOf(".") !== -1) { + //Определяем секунды + let seconds = 24 * 60 * 60 * timeStamp; + //Определяем часы + const hours = Math.trunc(seconds / (60 * 60)); + //Переопределяем секунды + seconds = seconds % (60 * 60); + //Определяем минуты + const minutes = Math.trunc(seconds / 60); + //Определяем остаток секунд + seconds = Math.round(seconds % 60); + //Форматируем + const formattedTime = ("0" + hours).slice(-2) + ":" + ("0" + minutes).slice(-2) + ":" + ("0" + seconds).slice(-2); + //Возвращаем результат + return formattedTime; + } + return timeStamp; +}; + +//Считывание значений из локального хранилища +export const getLocalStorageValue = (sName, defaultValue = null) => { + return localStorage.getItem(sName) ? localStorage.getItem(sName) : defaultValue; +}; + +//Форматирование фильтра в массив для отбора +export const convertFilterValuesToArray = filterValues => { + //Инициализируем значение "с" состояния ("Все", "Не аннулированные" - 0, "Аннулированые" - 1) + let nClosedFrom = filterValues.sState ? ([EVENT_STATES[0], EVENT_STATES[1]].includes(filterValues.sState) ? 0 : 1) : 0; + //Инициализируем значение "по" состояния ("Все", "Аннулированные" - 1, "Не аннулированные" - 0) + let nClosedTo = filterValues.sState ? ([EVENT_STATES[0], EVENT_STATES[2]].includes(filterValues.sState) ? 1 : 0) : 0; + //Формируем массив значений фильтра + let filterValuesArray = [ + { name: "NCLOSED", from: nClosedFrom, to: nClosedTo }, + { name: "SEVTYPE_CODE", from: filterValues.sType, to: null }, + { name: "NCRN", from: filterValues.sCrnRnList, to: null }, + { name: "SSEND_PERSON", from: filterValues.sSendPerson, to: null }, + { name: "SSEND_DIVISION", from: filterValues.sSendDivision, to: null }, + { name: "SSEND_USRGRP", from: filterValues.sSendUsrGrp, to: null }, + { name: "NLINKED_RN", from: filterValues.sDocLink, to: null } + ]; + return filterValuesArray; +}; + +//Формирование массива действий карточки события +export const makeCardActionsArray = (onEdit, onEditClient, onDelete, onStateChange, onReturn, onSend, onNotesOpen, onFileLinksOpen) => { + //Формируем список действий карточки + return [ + { + method: "EDIT", + name: "Исправить", + icon: "edit", + visible: false, + delimiter: false, + tasksReload: false, + needAccountsReload: false, + func: onEdit + }, + { + method: "EDIT_CLIENT", + name: "Исправить в разделе", + icon: "edit_note", + visible: true, + delimiter: false, + tasksReload: false, + needAccountsReload: false, + func: onEditClient + }, + { + method: "DELETE", + name: "Удалить", + icon: "delete", + visible: true, + delimiter: true, + tasksReload: true, + needAccountsReload: false, + func: onDelete + }, + { + method: "TASK_STATE_CHANGE", + name: "Перейти", + icon: "turn_right", + visible: true, + delimiter: false, + tasksReload: true, + needAccountsReload: true, + func: onStateChange + }, + { + method: "TASK_RETURN", + name: "Выполнить возврат", + icon: "turn_left", + visible: true, + delimiter: false, + tasksReload: true, + needAccountsReload: true, + func: onReturn + }, + { + method: "TASK_SEND", + name: "Направить", + icon: "send", + visible: true, + delimiter: true, + tasksReload: true, + needAccountsReload: true, + func: onSend + }, + { + method: "NOTES", + name: "Примечания", + icon: "event_note", + visible: true, + delimiter: true, + tasksReload: false, + needAccountsReload: false, + func: onNotesOpen + }, + { + method: "FILE_LINKS", + name: "Присоединенные документы", + icon: "attach_file", + visible: true, + delimiter: false, + tasksReload: false, + needAccountsReload: false, + func: onFileLinksOpen + } + ]; +}; diff --git a/app/panels/clnt_task_board/styles.js b/app/panels/clnt_task_board/styles.js new file mode 100644 index 0000000..41da40a --- /dev/null +++ b/app/panels/clnt_task_board/styles.js @@ -0,0 +1,48 @@ +/* + Парус 8 - Панели мониторинга - УДП - Доски задач + Компонент: Общие стили +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import { APP_STYLES } from "../../../app.styles"; //Типовые стили + +//--------- +//Константы +//--------- + +//Общие стили +export const COMMON_STYLES = { + TASK_FORM_TEXT_FIELD: (widthVal, greyDisabled = false) => ({ + margin: "4px", + ...(widthVal ? { width: widthVal } : {}), + ...(greyDisabled + ? { + "& .MuiInputBase-input.Mui-disabled": { + WebkitTextFillColor: "rgba(0, 0, 0, 0.87)" + }, + "& .MuiInputLabel-root.Mui-disabled": { + WebkitTextFillColor: "rgba(0, 0, 0, 0.6)" + } + } + : {}) + }), + BOX_WITH_LEGEND: { border: "1px solid #939393" }, + BOX_SINGLE_COLUMN: { display: "flex", flexDirection: "column", gap: "10px" }, + LEGEND: { textAlign: "left" }, + SELECT_MENU: width => { + return { overflowY: "auto", ...APP_STYLES.SCROLL, width: width ? width : null }; + }, + STACK_DOCLINKS: { alignItems: "baseline" }, + SCROLL: { ...APP_STYLES.SCROLL, overflowY: "auto" }, + DIALOG_ACTIONS: { justifyContent: "end", paddingRight: "24px", paddingLeft: "24px" }, + DIALOG_CLOSE_BUTTON: { + position: "absolute", + right: 8, + top: 8, + color: theme => theme.palette.grey[500] + }, + ZERO_PADDING: { padding: 0 } +}; diff --git a/app/panels/clnt_task_board/task_dialog.js b/app/panels/clnt_task_board/task_dialog.js new file mode 100644 index 0000000..7591f9a --- /dev/null +++ b/app/panels/clnt_task_board/task_dialog.js @@ -0,0 +1,180 @@ +/* + Парус 8 - Панели мониторинга - УДП - Доски задач + Компонент панели: Диалог формы события +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React, { useState, useCallback, useContext } from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента +import { Dialog, DialogContent, DialogActions, Button } from "@mui/material"; //Интерфейсные компоненты +import { useClientEvent } from "./hooks/task_dialog_hooks"; //Хук для события +import { TaskForm } from "./components/task_form"; //Форма события +import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером +import { object2Base64XML } from "../../core/utils"; //Вспомогательные функции +import { COMMON_STYLES } from "./styles"; //Общие стили + +//--------- +//Константы +//--------- + +//Стили +const STYLES = { + DIALOG_CONTENT: { + paddingBottom: "0px", + maxHeight: "740px", + minHeight: "740px", + ...COMMON_STYLES.SCROLL + } +}; + +//----------- +//Тело модуля +//----------- + +//Диалог формы события +const TaskDialog = ({ taskRn, taskType, taskStatus, editable, onTasksReload, onClose }) => { + //Собственное состояние + const [task, setTask] = useClientEvent(taskRn, taskType, taskStatus); + + //Состояние заполненности всех обязательных свойств + const [dpReady, setDPReady] = useState(false); + + //Подключение к контексту взаимодействия с сервером + const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx); + + //При изменении заполненности всех обязательных свойств + const handleDPReady = useCallback(v => setDPReady(v), []); + + //При изменении информации о задаче + const handleTaskChange = useCallback( + newTaskValues => { + setTask(pv => ({ ...pv, ...newTaskValues })); + }, + [setTask] + ); + + //При добавлении события + const handleInsertTask = async callBack => { + await executeStored({ + stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_INSERT", + args: { + SCRN: task.sCrn, + SPREF: task.sPrefix, + SNUMB: task.sNumber, + STYPE: task.sType, + SSTATUS: task.sStatus, + SPLAN_DATE: task.dPlanDate, + SINIT_PERSON: task.sInitClnperson, + SCLIENT_CLIENT: task.sClntClients, + SCLIENT_PERSON: task.sClntClnperson, + SDESCRIPTION: task.sDescription, + SREASON: task.sInitReason, + CPROPS: { + VALUE: object2Base64XML( + [ + Object.fromEntries( + Object.entries(task.docProps) + // eslint-disable-next-line no-unused-vars + .filter(([_, v]) => v != (null || "")) + ) + ], + { + arrayNodeName: "props" + } + ), + SDATA_TYPE: SERV_DATA_TYPE_CLOB + } + } + }); + callBack(); + }; + + //При исправлении события + const handleUpdateEvent = async callBack => { + await executeStored({ + stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_UPDATE", + args: { + NCLNEVENTS: task.nRn, + SCLIENT_CLIENT: task.sClntClients, + SCLIENT_PERSON: task.sClntClnperson, + SDESCRIPTION: task.sDescription, + CPROPS: { + // eslint-disable-next-line no-unused-vars + VALUE: object2Base64XML([Object.fromEntries(Object.entries(task.docProps).filter(([_, v]) => v != (null || "")))], { + arrayNodeName: "props" + }), + SDATA_TYPE: SERV_DATA_TYPE_CLOB + } + } + }); + callBack(); + }; + + //При считывании следующего номера события + const handleEventNextNumbGet = useCallback(async () => { + //Считываем данные + const data = await executeStored({ + stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_NEXTNUMB_GET", + args: { + SPREFIX: task.sPrefix + } + }); + //Если данные есть + if (data) { + //Устанавливаем номер + setTask(pv => ({ ...pv, sNumber: data.SEVENT_NUMB })); + } + }, [executeStored, setTask, task.sPrefix]); + + //Генерация содержимого + return ( + + + + + {onClose ? ( + + {taskRn ? ( + + ) : ( + + )} + + + ) : null} + + ); +}; + +//Контроль свойств - Диалог формы события +TaskDialog.propTypes = { + taskRn: PropTypes.number, + taskType: PropTypes.string.isRequired, + taskStatus: PropTypes.string, + editable: PropTypes.bool, + onTasksReload: PropTypes.func.isRequired, + onClose: PropTypes.func.isRequired +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { TaskDialog }; diff --git a/db/PKG_P8PANELS_CLNTTSKBRD.pck b/db/PKG_P8PANELS_CLNTTSKBRD.pck new file mode 100644 index 0000000..c6ebc49 --- /dev/null +++ b/db/PKG_P8PANELS_CLNTTSKBRD.pck @@ -0,0 +1,2705 @@ +create or replace package PKG_P8PANELS_CLNTTSKBRD as + + /* Формирование набора данных с отобранными событиями */ + procedure CLNEVENTS_LOAD + ( + CFILTERS in clob, -- Фильтры + CORDERS in clob, -- Сортировки + NINCLUDE_ACCOUNTS in number := 1, -- Включать информацию о контрагентах с изображением (0 - нет, 1 - да) + COUT out clob -- Сериализованная таблица данных + ); + + /* Выбор события по рег. номеру */ + procedure CLNEVENTS_SELECT + ( + NCLNEVENTS in number, -- Рег. номер события + NIDENT out number -- Идентификатор буфера подобранных (списка отмеченных записей, null - не найдено) + ); + + /* Считывание следующего номера события */ + procedure CLNEVENTS_NEXTNUMB_GET + ( + SPREFIX in varchar2, -- Префикс события + SEVENT_NUMB out varchar2 -- Номер события + ); + + /* Считывание параметров события */ + procedure CLNEVENTS_GET + ( + NCLNEVENTS in number, -- Рег. номер события + COUT out clob -- Данные о записи события + ); + + /* Инициализация параметров события */ + procedure CLNEVENTS_INIT + ( + SEVENT_TYPE in varchar2, -- Мнемокод типа события + SPREF out varchar2, -- Префикс события + SNUMB out varchar2, -- Номер события + SINIT_PERSON out varchar2, -- Сотрудник - инициатор + SINIT_AUTHNAME out varchar2 -- Пользователь - инициатор + ); + + /* Добавление значений свойств документа */ + procedure CLNEVENTS_PROPS_INSERT + ( + NCLNEVENTS in number, -- Рег. номер события + SEVENT_TYPE in varchar2, -- Мнемокод типа события + CPROPS in clob -- Свойства документа + ); + + /* Добавление события */ + procedure CLNEVENTS_INSERT + ( + SCRN in varchar2, -- Наименование каталога + SPREF in varchar2, -- Префикс события + SNUMB in varchar2, -- Номер события + STYPE in varchar2, -- Тип события + SSTATUS in varchar2, -- Статус события + SPLAN_DATE in varchar2, -- Дата планируемого начала работ + SINIT_PERSON in varchar2, -- Инициатор + SCLIENT_CLIENT in varchar2, -- Клиент-инициатор + SCLIENT_PERSON in varchar2, -- Сотрудник-инициатор + SDESCRIPTION in varchar2, -- Описание + SREASON in varchar2, -- Причина + CPROPS in clob -- Свойства + ); + + /* Исправление события */ + procedure CLNEVENTS_UPDATE + ( + NCLNEVENTS in number, -- Рег. номер события + SCLIENT_CLIENT in varchar2, -- Клиент-инициатор + SCLIENT_PERSON in varchar2, -- Сотрудник-инициатор + SDESCRIPTION in varchar2, -- Описание + CPROPS in clob -- Свойства + ); + + /* Удаление события */ + procedure CLNEVENTS_DELETE + ( + NCLNEVENTS in number -- Рег. номер события + ); + + /* Изменение статуса события */ + procedure CLNEVENTS_STATE_CHANGE + ( + NIDENT in out number, -- Рег. номер отмеченных записей + NSTEP in out number, -- Шаг выполнения (0-прекратить выполнение, 1,2..- номер шага) + NEVENT in out number, -- Событие, для отбора исполнителей + SEVENT_TYPE in out varchar2, -- Тип события, для отбора точек перехода и исполнителей + SEVENT_STAT in out varchar2, -- Cтатус события, текущий - для отбора точек перехода, следующий - для отбора исполнителей + SINIT_PERSON in out varchar2, -- Инициатор–сотрудник, для отбора исполнителей + SINIT_AUTHNAME in out varchar2, -- Инициатор–пользователь, для отбора исполнителей + SCLIENT_CLIENT in out varchar2, -- Клиент–организация, для отбора исполнителей + SCLIENT_PERSON in out varchar2, -- Клиент–сотрудник, для отбора исполнителей + NPOINT in out number, -- Текущая точка маршрута + NPASS in number, -- Точка перехода + NSELECT_EXEC out number, -- Признак необходимости выбора исполнителя + SEXECUTEMETHOD out varchar2, -- Мнемокод метода + SSEND_CLIENT in varchar2, -- Направить – организация + SSEND_DIVISION in varchar2, -- Направить – подразделение + SSEND_POST in varchar2, -- Направить – должность + SSEND_PERFORM in varchar2, -- Направить – должность в подразделении + SSEND_PERSON in varchar2, -- Направить – сотрудник + SSEND_STAFFGRP in varchar2, -- Направить – нештатная структура + SSEND_USER_GROUP in varchar2, -- Направить – группа пользователей + SSEND_USER_NAME in varchar2, -- Направить – пользователь + NSEND_PREDEFINED_EXEC in number, -- Переадресация + NSEND_PREDEFINED_PROC in number, -- Процедура выбора предопределенного исполнителя + SNEXT_STAT in varchar2 := null, -- Следующий статус (при переносе события) + SNOTE_HEADER in varchar2 := null, -- Заголовок примечания (при наличии) + SNOTE in varchar2 := null -- Примечание (при наличии) + ); + + /* Переадресация события */ + procedure CLNEVENTS_SEND + ( + NIDENT in out number, -- Рег. номер отмеченных записей + NSTEP in out number, -- Шаг выполнения + NEVENT in out number, -- Событие, для отбора исполнителей + SEVENT_TYPE in out varchar2, -- Тип события, для отбора точек перехода и исполнителей + SEVENT_STAT in out varchar2, -- Текущий статус события, для отбора исполнителей + SINIT_PERSON in out varchar2, -- Инициатор–сотрудник, для отбора исполнителей + SINIT_AUTHNAME in out varchar2, -- Инициатор–пользователь, для отбора исполнителей + SCLIENT_CLIENT in out varchar2, -- Клиент–организация, для отбора исполнителей + SCLIENT_PERSON in out varchar2, -- Клиент–сотрудник, для отбора исполнителей + SEXECUTEMETHOD out varchar2, -- Мнемокод метода + SSEND_CLIENT in varchar2, -- Направить – организация + SSEND_DIVISION in varchar2, -- Направить – подразделение + SSEND_POST in varchar2, -- Направить – должность + SSEND_PERFORM in varchar2, -- Направить – должность в подразделении + SSEND_PERSON in out varchar2, -- Направить – сотрудник + SSEND_STAFFGRP in varchar2, -- Направить – нештатная структура + SSEND_USER_GROUP in varchar2, -- Направить – группа пользователей + SSEND_USER_NAME in out varchar2, -- Направить – пользователь + NSEND_PREDEFINED_EXEC in number, -- Переадресация + NSEND_PREDEFINED_PROC in number, -- Процедура выбора предопределенного исполнителя + SNOTE_HEADER in varchar2 := null, -- Заголовок примечания (при наличии) + SNOTE in varchar2 := null -- Примечание (при наличии) + ); + + /* Возврат в предыдущую точку события */ + procedure CLNEVENTS_RETURN + ( + NCLNEVENTS in number -- Рег. номер события + ); + + /* Считывание маршрутов и исполнителей события */ + procedure CLNEVENTS_GET_INFO_BY_CODE + ( + SEVNTYPE_CODE in varchar2, -- Мнемокод типа события + COUT out clob -- XML с параметрами фильтра по умолчанию + ); + + /* Считывание учётных документов события */ + procedure CLNEVENTS_DOCLINKS_GET + ( + SEVNTYPE_CODE in varchar2, -- Мнемокод типа события + COUT out clob -- XML с параметрами фильтра по умолчанию + ); + + /* Считывание списка рег. номеров дочерних каталогов (включая основной) */ + procedure CLNEVENTS_SUBCATALOGS_GET + ( + SCRN_NAME in varchar2, -- Наименование каталога + NSUBCAT in number := 0, -- Признак включения подкаталогов (0 - нет, 1 - да) + SRESULT out varchar2 -- Список рег. номеров каталогов + ); + + /* Формирование условий отбора фильтра */ + procedure CLNEVENTS_COND; + + /* Считывание настройки раздела "События" для пользователя */ + procedure CLNEVENTS_DP_RULES_GET + ( + COUT out clob -- XML с настройкой раздела + ); + + /* Считывание свойств раздела "События" */ + procedure CLNEVENTS_PROPS_GET + ( + SEVNTYPE_CODE in varchar2, -- Мнемокод типа события + COUT out clob -- XML со свойствами раздела + ); + + /* Загрузка статусов типа события */ + procedure CLNEVNSTATS_LOAD + ( + SCLNEVNTYPES in varchar2, -- Мнемокод типа события + COUT out clob -- Статусы типа события + ); + +end PKG_P8PANELS_CLNTTSKBRD; +/ +create or replace package body PKG_P8PANELS_CLNTTSKBRD as + + /* Константы - мнемокоды разделов */ + SUNIT_CLNEVENTS constant PKG_STD.TSTRING := 'ClientEvents'; -- Раздел "События" + SUNIT_AGNLIST constant PKG_STD.TSTRING := 'AGNLIST'; -- Раздел "Контрагенты" + SUNIT_DP constant PKG_STD.TSTRING := 'DocsProperties'; -- Раздел "Свойства документов" + SSELECTLIST_ACTION constant PKG_STD.TSTRING := 'P8Panels'; -- Код действия для селектлиста + + + /* Константы - вспомогательные */ + SDATE_PATTERN_HH_MI constant PKG_STD.TSTRING := 'dd.mm.yyyy hh24:mi'; -- Дата и время без секунд + + /* Считывание префикса типа свойства документа */ + function UTL_DP_PREF_TYPE_GET + ( + NTYPE in number, -- Тип свойства документа + NSHORT in number := 1 -- Признак сокращенного префикса (0 - длинный "SDP", "NDP", "DDP", 1 - сокращенный "S", "N", "D") + ) return varchar2 -- Префикс свойства документа + is + SRESULT PKG_STD.TSTRING; -- Префикс свойства документа + begin + /* Если требуется сокращенный */ + if (NSHORT = 1) then + /* Тип строка */ + case + /* Строка */ + when (NTYPE = 0) then + SRESULT := 'S'; + /* Число или время */ + when ((NTYPE = 1) or (NTYPE = 3)) then + SRESULT := 'N'; + /* Дата */ + else + SRESULT := 'D'; + end case; + else + /* Тип строка */ + case + /* Строка */ + when (NTYPE = 0) then + SRESULT := 'SDP'; + /* Число или время */ + when ((NTYPE = 1) or (NTYPE = 3)) then + SRESULT := 'NDP'; + /* Дата */ + else + SRESULT := 'DDP'; + end case; + end if; + /* Возвращаем результат */ + return SRESULT; + end UTL_DP_PREF_TYPE_GET; + + /* Считывание отформатированного ID доп. свойства (формат "_<РЕГ_НОМЕР>") */ + function UTL_DP_FORMATTED_ID_GET + ( + NTYPE in number, -- Тип свойства документа + NRN in number -- Рег. номер свойства документа + ) return varchar2 -- Отформатированный ID (формат "_<РЕГ_НОМЕР>") + is + begin + /* Возвращаем результат */ + return UTL_DP_PREF_TYPE_GET(NTYPE => NTYPE, NSHORT => 0) || '_' || TO_CHAR(NRN); + end UTL_DP_FORMATTED_ID_GET; + + /* Считывание записи события */ + function UTL_CLNEVENTS_GET + ( + NCOMPANY in number, -- Рег. номер организации + NCLNEVENTS in number -- Рег. номер события + ) return CLNEVENTS%rowtype -- Запись события + is + RRESULT CLNEVENTS%rowtype; -- Запись события + begin + /* Считываем событие */ + begin + select T.* + into RRESULT + from CLNEVENTS T + where T.RN = NCLNEVENTS + and T.COMPANY = NCOMPANY; + exception + when others then + P_EXCEPTION(0, + 'Ошибка считывания записи события с рег. номером (%s).', + NCLNEVENTS); + end; + /* Возвращаем результат */ + return RRESULT; + end UTL_CLNEVENTS_GET; + + /* Считывание мнемокодов ссылок события */ + procedure UTL_CLNEVENTS_JOINS_GET + ( + NCLNEVENTS in number, -- Рег. номер события, + STYPE out varchar2, -- Мнемокод типа события + SSTATUS out varchar2, -- Мнемокод статуса типа события + SINIT_PERSON out varchar2, -- Мнемокод инициатора сотрудника + SCLIENT_CLIENT out varchar2, -- Мнемокод клиента (организации) + SCLIENT_PERSON out varchar2, -- Мнемокод клиента (сотрудника) + SSEND_CLIENT out varchar2, -- Мнемокод направления (организация) + SSEND_DIVISION out varchar2, -- Мнемокод направления (подразделение) + SSEND_POST out varchar2, -- Мнемокод направления (должность) + SSEND_PERFORM out varchar2, -- Мнемокод направления (должность в подразделении) + SSEND_PERSON out varchar2, -- Мнемокод направления (сотрудник) + SSEND_STAFFGRP out varchar2, -- Мнемокод направления (нештатная структура) + SSEND_USER_GROUP out varchar2, -- Мнемокод направления (группа пользователей) + SSEND_USER_NAME out varchar2 -- Мнемокод направления (группа пользователь) + ) + is + begin + /* Считываем все мнемокоды */ + begin + select ET.EVNTYPE_CODE, + TS.EVNSTAT_CODE, + F_CLNPERSONS_FORMAT_CODE(IP.COMPANY, IP.CODE), + CC.CLIENT_CODE, + F_CLNPERSONS_FORMAT_CODE(CP.COMPANY, CP.CODE), + SC.CLIENT_CODE, + ID.CODE, + PS.CODE, + F_CLNPSDEP_FORMAT_CODE(PP.COMPANY, PP.CODE), + F_CLNPERSONS_FORMAT_CODE(SP.COMPANY, SP.CODE), + SG.CODE, + UG.CODE, + UL3.NAME + into STYPE, + SSTATUS, + SINIT_PERSON, + SCLIENT_CLIENT, + SCLIENT_PERSON, + SSEND_CLIENT, + SSEND_DIVISION, + SSEND_POST, + SSEND_PERFORM, + SSEND_PERSON, + SSEND_STAFFGRP, + SSEND_USER_GROUP, + SSEND_USER_NAME + from CLNEVENTS T, + CLNEVNTYPES ET, + CLNEVNTYPSTS ES, + CLNEVNSTATS TS, + CLNPERSONS IP, + CLNCLIENTS CC, + CLNPERSONS CP, + CLNCLIENTS SC, + INS_DEPARTMENT ID, + CLNPOSTS PS, + CLNPSDEP PP, + CLNPERSONS SP, + FCSTAFFGRP SG, + USERGRP UG, + USERLIST UL3 + where T.RN = NCLNEVENTS + and T.EVENT_TYPE = ET.RN + and T.EVENT_STAT = ES.RN + and ES.EVENT_STATUS = TS.RN + and T.INIT_PERSON = IP.RN(+) + and T.CLIENT_CLIENT = CC.RN(+) + and T.CLIENT_PERSON = CP.RN(+) + and T.SEND_CLIENT = SC.RN(+) + and T.SEND_DIVISION = ID.RN(+) + and T.SEND_POST = PS.RN(+) + and T.SEND_PERFORM = PP.RN(+) + and T.SEND_PERSON = SP.RN(+) + and T.SEND_STAFFGRP = SG.RN(+) + and T.SEND_USER_GROUP = UG.RN(+) + and T.SEND_USER_AUTHID = UL3.AUTHID(+); + exception + when others then + P_EXCEPTION(0, 'Ошибка считывания записи события с рег. номером (%s).', NCLNEVENTS); + end; + end UTL_CLNEVENTS_JOINS_GET; + + /* Считывание наименования исполнителя события */ + function UTL_CLNEVENTS_SENDER_GET + ( + NCLNEVENTS in number -- Рег. номер события + ) return varchar2 -- Наименование исполнителя + is + SRESULT PKG_STD.TSTRING; -- Наименование исполнителя + NCOMPANY PKG_STD.TREF := GET_SESSION_COMPANY(); -- Рег. номер организации + NAGN_VER PKG_STD.TREF; -- Версия раздела "Контрагенты" + begin + /* Считываем версию организации */ + FIND_VERSION_BY_COMPANY(NCOMPANY => NCOMPANY, SUNITCODE => SUNIT_AGNLIST, NVERSION => NAGN_VER); + /* Считываем наименование исполнителя и аватар (если есть возможность) */ + begin + select COALESCE(SC_A.AGNABBR, + ID.NAME, + PP.PSDEP_NAME, + PS.NAME, + SP_A.AGNABBR, + SG.NAME, + UG.NAME, + SUA.AGNABBR, + TA.AGNABBR) + into SRESULT + from CLNEVENTS T, + CLNCLIENTS SC, + AGNLIST SC_A, + INS_DEPARTMENT ID, + CLNPSDEP PP, + CLNPOSTS PS, + CLNPERSONS SP, + AGNLIST SP_A, + FCSTAFFGRP SG, + USERGRP UG, + AGNLIST TA, + AGNLIST SUA + where T.RN = NCLNEVENTS + and T.COMPANY = NCOMPANY + and T.SEND_CLIENT = SC.RN(+) + and SC.CLIENT_AGENT = SC_A.RN(+) + and T.SEND_DIVISION = ID.RN(+) + and T.SEND_PERFORM = PP.RN(+) + and T.SEND_POST = PS.RN(+) + and T.SEND_PERSON = SP.RN(+) + and SP.PERS_AGENT = SP_A.RN(+) + and T.SEND_STAFFGRP = SG.RN(+) + and T.SEND_USER_GROUP = UG.RN(+) + and (T.SEND_USER_AUTHID = SUA.PERS_AUTHID(+) and NAGN_VER = SUA.VERSION(+)) + and (T.AUTHID = TA.PERS_AUTHID(+) and NAGN_VER = TA.VERSION(+)); + exception + when others then + SRESULT := null; + end; + /* Возвращаем результат */ + return SRESULT; + end UTL_CLNEVENTS_SENDER_GET; + + /* Проверка возможности редактирования события в текущем статусе */ + function UTL_CLNEVENTS_BAN_UPDATE_GET + ( + NCOMPANY in number, -- Рег. номер организации + NCLNEVENTS in number -- Рег. номер события + ) return number -- Признак запрета исправления события в точке маршрута (0 - нет, 1 - да) + is + NRESULT PKG_STD.TNUMBER; -- Признак запрета исправления события в точке маршрута (0 - нет, 1 - да) + begin + /* Считываем событие */ + begin + select EP.BAN_UPDATE + into NRESULT + from CLNEVENTS CE, + EVRTPOINTS EP + where CE.RN = NCLNEVENTS + and CE.COMPANY = NCOMPANY + and EP.EVENT_STATUS = CE.EVENT_STAT; + exception + when others then + P_EXCEPTION(0, + 'Ошибка считывания записи события с рег. номером (%s).', + NCLNEVENTS); + end; + /* Возвращаем результат */ + return NRESULT; + end UTL_CLNEVENTS_BAN_UPDATE_GET; + + /* Формирование блока запроса свойств документа и коллекций наименований/типов */ + procedure UTL_CLNEVENTS_DP_QUERY_BLOCK + ( + NCOMPANY in number, -- Рег. номер организации + NCLNEVNTYPES in number, -- Рег. номер типа события + NQUERY_POS in number, -- Признак позиции блока в запросе (0 - блок находится в конце запроса, 1 - за блоком есть параметры) + CSQL out clob, -- Буфер для блока SQL + TDP_NAMES in out nocopy PKG_CONTVALLOC1S.TCONTAINER, -- Коллекция с наименованиями + TDP_TYPES in out nocopy PKG_CONTVALLOC1S.TCONTAINER -- Коллекция с типами данных + ) + is + SDP_CODE PKG_STD.TSTRING; -- Код свойства документа + begin + /* Цикл по свойствам документа раздела */ + for REC in (select CEP.PROPERTY, + DP.NAME, + DP.FORMAT + from CLEVTPROPS CEP, + DOCS_PROPS DP + where CEP.PRN = NCLNEVNTYPES + and CEP.COMPANY = NCOMPANY + and CEP.PROPERTY = DP.RN(+) + order by TO_CHAR(PROPERTY)) + loop + /* Формирования кода */ + SDP_CODE := 'DP_' || REC.PROPERTY; + /* Добавление данных в коллекции */ + PKG_CONTVALLOC1S.PUTS(RCONTAINER => TDP_NAMES, SROWID => SDP_CODE, SVALUE => REC.NAME); + PKG_CONTVALLOC1S.PUTN(RCONTAINER => TDP_TYPES, SROWID => SDP_CODE, NVALUE => REC.FORMAT); + /* Формирование запроса */ + PKG_SQL_BUILD.APPEND(SSQL => CSQL, + SELEMENT1 => ' (select COALESCE(V.STR_VALUE, to_char(V.NUM_VALUE), to_char(V.DATE_VALUE))'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' from DOCS_PROPS_VALS V'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' where T.RN = V.UNIT_RN(+)'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' and V.DOCS_PROP_RN(+) = ' || REC.PROPERTY || ') as "' || SDP_CODE || '",'); + end loop; + /* Если собрали данные */ + if ((CSQL is not null) and (CSQL <> EMPTY_CLOB())) then + /* Добавляем запятую перед встраиваемым блоком */ + CSQL := ',' || CSQL; + /* Если блок находится в конце запроса */ + if (NQUERY_POS = 0) then + /* Удаляем последнюю запятую */ + CSQL := SUBSTR(CSQL, 1, LENGTH(CSQL) - 1); + end if; + end if; + end UTL_CLNEVENTS_DP_QUERY_BLOCK; + + /* Считывание мнемокода статуса типа события по умолчанию */ + function UTL_CLNEVNSTATS_CODE_GET + ( + NCOMPANY in number, -- Рег. номер организации + SEVENT_TYPE in varchar2 -- Мнемокод типа события + ) return varchar2 -- Мнемокод статуса типа события по умолчанию + is + SRESULT PKG_STD.TSTRING; -- Мнемокод статуса типа события по умолчанию + begin + /* Считываем мнемокод статуса типа события по умолчанию */ + begin + select TMP.EVNSTAT_CODE + into SRESULT + from (select S.EVNSTAT_CODE + from CLNEVNTYPES T, + CLNEVNTYPSTS TS, + CLNEVNSTATS S + where T.COMPANY = NCOMPANY + and T.EVNTYPE_CODE = SEVENT_TYPE + and TS.PRN = T.RN + and F_EVRTPOINTS_VALID_START_POINT(T.COMPANY, T.RN, TS.RN, TS.DEFAULT_STATUS) = 1 + and TS.EVENT_STATUS = S.RN + order by TS.DEFAULT_STATUS desc, + S.EVNSTAT_CODE) TMP + where ROWNUM = 1; + exception + when others then + SRESULT := null; + end; + /* Возвращаем результат */ + return SRESULT; + end UTL_CLNEVNSTATS_CODE_GET; + + /* Считывание описателя документа */ + function UTL_UNITLIST_DESCR_GET + ( + SUNIT_CODE in varchar2, -- Код раздела + NDOCUMENT in number -- Рег. номер документа в разделе + ) return varchar2 -- Описатель + is + SRES PKG_STD.TLSTRING; -- Буфер для результата + SDESCR_DEFAULT PKG_STD.TSTRING := 'Документ раздела '; -- Описатель по умолчанию + NDOCDESCR_RN PKG_STD.TREF; -- Рег. номер описателя + NDESCR_FOUND PKG_STD.TNUMBER := 1; -- Признак существования описателя + begin + /* Считываем рег. номер описателя */ + begin + select T.RN + into NDOCDESCR_RN + from DOCDESCRS T + where T.UNITCODE = SUNIT_CODE + and T.USERPROC is not null + and ROWNUM <= 1; + exception + when NO_DATA_FOUND then + NDESCR_FOUND := 0; + end; + /* Если есть и описатель и передан идентификатор документа */ + if ((NDESCR_FOUND = 1) and (NDOCUMENT is not null)) then + /* Исполним и вернём описатель */ + begin + SRES := F_DOCDESCRS_GET_DESCRIPTION(SUNITCODE => SUNIT_CODE, NDOCUMENT => NDOCUMENT); + return SRES; + exception + when others then + return COALESCE(SDESCR_DEFAULT, 'Идентификатор - ' || TO_CHAR(NDOCUMENT)); + end; + else + /* Описателя нет, но есть идентификатор документа */ + if (NDOCUMENT is not null) then + /* Тогда вернём описатель по умолчанию или идентификатор */ + return COALESCE(SDESCR_DEFAULT, 'Идентификатор - ' || TO_CHAR(NDOCUMENT)); + else + /* Нет ничего */ + return SDESCR_DEFAULT; + end if; + end if; + end UTL_UNITLIST_DESCR_GET; + + /* Формирование набора данных с отобранными событиями */ + procedure CLNEVENTS_DG_GET + ( + NIDENT in number, -- Идентификатор отбора + CFILTERS in clob, -- Фильтры + CORDERS in clob, -- Сортировки + COUT out clob -- Сериализованная таблица данных + ) + is + NCOMPANY PKG_STD.TREF := GET_SESSION_COMPANY(); -- Организация сеанса + RF PKG_P8PANELS_VISUAL.TDG_FILTERS; -- Фильтры + RO PKG_P8PANELS_VISUAL.TDG_ORDERS; -- Сортировки + RDG PKG_P8PANELS_VISUAL.TDG; -- Описание таблицы + RDG_ROW PKG_P8PANELS_VISUAL.TDG_ROW; -- Строка таблицы + CSQL clob; -- Буфер для запроса + ICURSOR integer; -- Курсор для исполнения запроса + SEVSTAT PKG_STD.TSTRING; -- Буфер для статуса события + NCLNEVNTYPES PKG_STD.TREF; -- Рег. номер типа события + SDOC_PREF PKG_STD.TSTRING; -- Префикс события (без пробелов) + SDOC_NUMB PKG_STD.TSTRING; -- Номер события (без пробелов) + NCLNEVENTS PKG_STD.TREF; -- Рег. номер события + SSENDER PKG_STD.TSTRING; -- Наименование исполнителя + CDP_QUERY clob; -- Блок запроса со свойствами документа + TDP_NAMES PKG_CONTVALLOC1S.TCONTAINER; -- Коллекция с наименованиями свойств документа + TDP_TYPES PKG_CONTVALLOC1S.TCONTAINER; -- Коллекция с типами свойств документа + + /* Считывание типа события из фильтров */ + function CLNEVNTYPES_GET + ( + NCOMPANY in number, -- Рег. номер организации + RFILTERS in PKG_P8PANELS_VISUAL.TDG_FILTERS -- Фильтры + ) return number -- Рег. номер типа события + is + SCLNEVNTYPES PKG_STD.TSTRING; -- Мнемокод типа события + NRESULT PKG_STD.TREF; -- Рег. номер типа события + begin + /* Обходим коллекцию фильтров */ + for I in RFILTERS.FIRST .. RFILTERS.LAST + loop + /* Если это фильтр типа события */ + if (RFILTERS(I).SNAME = 'SEVTYPE_CODE') then + /* Считываем мнемокод типа события */ + SCLNEVNTYPES := RFILTERS(I).SFROM; + /* Выходим из цикла */ + exit; + end if; + end loop; + /* Если мнемокод типа события был считан */ + if (SCLNEVNTYPES is not null) then + /* Считываем рег. номер типа события */ + begin + select T.RN + into NRESULT + from CLNEVNTYPES T + where T.COMPANY = NCOMPANY + and T.EVNTYPE_CODE = SCLNEVNTYPES; + exception + when others then + NRESULT := null; + end; + end if; + /* Возвращаем результат */ + return NRESULT; + end CLNEVNTYPES_GET; + + /* Добавление колонок/значений по коллекции свойств документа */ + procedure RDG_FILL_BY_TDP + ( + RDG in out nocopy PKG_P8PANELS_VISUAL.TDG, -- Описание таблицы (изменяется при добавлении колонки) + RDG_ROW in out nocopy PKG_P8PANELS_VISUAL.TDG_ROW, -- Строка таблицы (изменяется при заполнении значений строки) + TDP_NAMES in PKG_CONTVALLOC1S.TCONTAINER, -- Коллекция с наименованиями свойств документа + TDP_TYPES in PKG_CONTVALLOC1S.TCONTAINER, -- Коллекция с типами свойств документа + NFIRST_NUMB in number := 1, -- Номер первого элемента коллекции + NLAST_NUMB in number, -- Номер последнего элемента коллекции + ICURSOR in integer := null, -- Курсор для исполнения запрос (требуется при заполнении значений строки) + NACTION in number := 0 -- Действие (0 - добавление колонки в таблицу, 1 - заполнение значений строки, 2 - добавление колонки курсора) + ) + is + SDP_CODE PKG_STD.TSTRING; -- Код свойства документа + SDP_NAME PKG_STD.TSTRING; -- Имя свойства документа + NDP_TYPE PKG_STD.TNUMBER; -- Тип свойства документа + SDP_PREF PKG_STD.TSTRING; -- Префикс свойства документа + begin + /* Получаем код первого элемента */ + SDP_CODE := PKG_CONTVALLOC1S.FIRST_(RCONTAINER => TDP_NAMES); + /* Цикл по именам доп. свойств */ + for I in NFIRST_NUMB .. NLAST_NUMB + loop + /* Получаем код следующего элемента */ + if (I > NFIRST_NUMB) then + SDP_CODE := PKG_CONTVALLOC1S.NEXT_(RCONTAINER => TDP_NAMES, SROWID => SDP_CODE); + end if; + /* Если это добавление колонки курсора */ + if (NACTION = 2) then + PKG_SQL_DML.DEFINE_COLUMN_STR(ICURSOR => ICURSOR, IPOSITION => I); + else + /* Считываем тип доп. свойства */ + NDP_TYPE := PKG_CONTVALLOC1S.GETN(RCONTAINER => TDP_TYPES, SROWID => SDP_CODE); + /* Определение префикса типа данных к коду */ + SDP_PREF := UTL_DP_PREF_TYPE_GET(NTYPE => NDP_TYPE); + /* Если это добавление колонки в таблицу */ + if (NACTION = 0) then + /* Считываем имя свойства документа */ + SDP_NAME := PKG_CONTVALLOC1S.GETS(RCONTAINER => TDP_NAMES, SROWID => SDP_CODE); + /* Добавляем колонку таблицы */ + PKG_P8PANELS_VISUAL.TDG_ADD_COL_DEF(RDATA_GRID => RDG, + SNAME => SDP_PREF || SDP_CODE, + SCAPTION => SDP_NAME, + SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_STR, + BVISIBLE => false); + else + PKG_P8PANELS_VISUAL.TDG_ROW_ADD_CUR_COLS(RROW => RDG_ROW, + SNAME => SDP_PREF || SDP_CODE, + ICURSOR => ICURSOR, + NPOSITION => I); + end if; + end if; + end loop; + end RDG_FILL_BY_TDP; + begin + /* Очистка коллекций информации о свойствах документа */ + PKG_CONTVALLOC1S.PURGE(RCONTAINER => TDP_NAMES); + PKG_CONTVALLOC1S.PURGE(RCONTAINER => TDP_TYPES); + /* Читаем фильтры */ + RF := PKG_P8PANELS_VISUAL.TDG_FILTERS_FROM_XML(CFILTERS => CFILTERS); + /* Читаем сортировки */ + RO := PKG_P8PANELS_VISUAL.TDG_ORDERS_FROM_XML(CORDERS => CORDERS); + /* Инициализируем таблицу данных */ + RDG := PKG_P8PANELS_VISUAL.TDG_MAKE(); + /* Считываем рег. номер типа события */ + NCLNEVNTYPES := CLNEVNTYPES_GET(NCOMPANY => NCOMPANY, RFILTERS => RF); + /* Если тип события не считан */ + if (NCLNEVNTYPES is null) then + P_EXCEPTION(0, + 'Ошибка считывания типа события. Фильтр по типу события обязателен.'); + end if; + /* Описываем колонки таблицы данных */ + PKG_P8PANELS_VISUAL.TDG_ADD_COL_DEF(RDATA_GRID => RDG, + SNAME => 'NRN', + SCAPTION => 'Рег. номер записи', + SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_NUMB, + BVISIBLE => false); + PKG_P8PANELS_VISUAL.TDG_ADD_COL_DEF(RDATA_GRID => RDG, + SNAME => 'NCRN', + SCAPTION => 'Каталог', + SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_STR, + SCOND_FROM => 'BYEV_CRN', + BFILTER => true, + BVISIBLE => false); + PKG_P8PANELS_VISUAL.TDG_ADD_COL_DEF(RDATA_GRID => RDG, + SNAME => 'SEVPREF', + SCAPTION => 'Префикс', + SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_STR, + BVISIBLE => false); + PKG_P8PANELS_VISUAL.TDG_ADD_COL_DEF(RDATA_GRID => RDG, + SNAME => 'SEVNUMB', + SCAPTION => 'Номер', + SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_STR, + BVISIBLE => false); + PKG_P8PANELS_VISUAL.TDG_ADD_COL_DEF(RDATA_GRID => RDG, + SNAME => 'SPREF_NUMB', + SCAPTION => 'Префикс и номер', + SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_STR, + BORDER => true, + BVISIBLE => false); + PKG_P8PANELS_VISUAL.TDG_ADD_COL_DEF(RDATA_GRID => RDG, + SNAME => 'NEVTYPE', + SCAPTION => 'Тип события (Рег. номер)', + SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_NUMB, + BVISIBLE => false); + PKG_P8PANELS_VISUAL.TDG_ADD_COL_DEF(RDATA_GRID => RDG, + SNAME => 'SEVTYPE_CODE', + SCAPTION => 'Тип события', + SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_STR, + SCOND_FROM => 'BYEV_TYPE', + BFILTER => true, + BVISIBLE => false); + PKG_P8PANELS_VISUAL.TDG_ADD_COL_DEF(RDATA_GRID => RDG, + SNAME => 'DREG_DATE', + SCAPTION => 'Дата регистрации', + SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_DATE, + BVISIBLE => false); + PKG_P8PANELS_VISUAL.TDG_ADD_COL_DEF(RDATA_GRID => RDG, + SNAME => 'DCHANGE_DATE', + SCAPTION => 'Дата последнего изменения', + SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_DATE, + BORDER => true, + BVISIBLE => false); + PKG_P8PANELS_VISUAL.TDG_ADD_COL_DEF(RDATA_GRID => RDG, + SNAME => 'DEXPIRE_DATE', + SCAPTION => 'Дата истечения времени нахождения в точке маршрута', + SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_DATE, + BVISIBLE => false); + PKG_P8PANELS_VISUAL.TDG_ADD_COL_DEF(RDATA_GRID => RDG, + SNAME => 'DPLAN_DATE', + SCAPTION => 'Дата начала работ', + SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_DATE, + BORDER => true, + BVISIBLE => false); + PKG_P8PANELS_VISUAL.TDG_ADD_COL_DEF(RDATA_GRID => RDG, + SNAME => 'NINIT_PERSON', + SCAPTION => 'Инициализатор (Рег. номер)', + SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_NUMB, + BVISIBLE => false); + PKG_P8PANELS_VISUAL.TDG_ADD_COL_DEF(RDATA_GRID => RDG, + SNAME => 'SINIT_PERSON', + SCAPTION => 'Инициализатор', + SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_STR, + BVISIBLE => false); + PKG_P8PANELS_VISUAL.TDG_ADD_COL_DEF(RDATA_GRID => RDG, + SNAME => 'NEVSTAT', + SCAPTION => 'Статус (Рег. номер)', + SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_NUMB, + BVISIBLE => false); + PKG_P8PANELS_VISUAL.TDG_ADD_COL_DEF(RDATA_GRID => RDG, + SNAME => 'SEVSTAT_NAME', + SCAPTION => 'Статус', + SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_STR, + BVISIBLE => false); + PKG_P8PANELS_VISUAL.TDG_ADD_COL_DEF(RDATA_GRID => RDG, + SNAME => 'NSEND_PERSON', + SCAPTION => 'Получатель (Рег. номер)', + SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_NUMB, + BVISIBLE => false); + PKG_P8PANELS_VISUAL.TDG_ADD_COL_DEF(RDATA_GRID => RDG, + SNAME => 'SSEND_PERSON', + SCAPTION => 'Получатель', + SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_STR, + SCOND_FROM => 'BYEV_S_PERSON', + BFILTER => true, + BVISIBLE => false); + PKG_P8PANELS_VISUAL.TDG_ADD_COL_DEF(RDATA_GRID => RDG, + SNAME => 'NAGNLIST_RN', + SCAPTION => 'Рег. номер контрагенты', + SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_NUMB, + BVISIBLE => false); + PKG_P8PANELS_VISUAL.TDG_ADD_COL_DEF(RDATA_GRID => RDG, + SNAME => 'NSEND_DIVISION', + SCAPTION => 'Подразделение (Рег. номер)', + SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_NUMB, + BVISIBLE => false); + PKG_P8PANELS_VISUAL.TDG_ADD_COL_DEF(RDATA_GRID => RDG, + SNAME => 'SSEND_DIVISION', + SCAPTION => 'Подразделение', + SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_STR, + SCOND_FROM => 'BYEV_S_DIVISION', + BFILTER => true, + BVISIBLE => false); + PKG_P8PANELS_VISUAL.TDG_ADD_COL_DEF(RDATA_GRID => RDG, + SNAME => 'NSEND_USRGRP', + SCAPTION => 'Группа пользователей (Рег. номер)', + SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_NUMB, + BVISIBLE => false); + PKG_P8PANELS_VISUAL.TDG_ADD_COL_DEF(RDATA_GRID => RDG, + SNAME => 'SSEND_USRGRP', + SCAPTION => 'Группа пользователей', + SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_STR, + SCOND_FROM => 'BYEV_S_USER_GROUP', + BFILTER => true, + BVISIBLE => false); + PKG_P8PANELS_VISUAL.TDG_ADD_COL_DEF(RDATA_GRID => RDG, + SNAME => 'SEVDESCR', + SCAPTION => 'Описание', + SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_STR, + BVISIBLE => false); + PKG_P8PANELS_VISUAL.TDG_ADD_COL_DEF(RDATA_GRID => RDG, + SNAME => 'NCLOSED', + SCAPTION => 'Аннулировано', + SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_NUMB, + SCOND_FROM => 'BYEV_CLOSED_From', + SCOND_TO => 'BYEV_CLOSED_To', + BFILTER => true, + BVISIBLE => false); + PKG_P8PANELS_VISUAL.TDG_ADD_COL_DEF(RDATA_GRID => RDG, + SNAME => 'SLINKED_UNIT', + SCAPTION => 'Учётный раздел', + SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_STR, + BVISIBLE => false); + PKG_P8PANELS_VISUAL.TDG_ADD_COL_DEF(RDATA_GRID => RDG, + SNAME => 'NLINKED_RN', + SCAPTION => 'Учётный документ (Рег. номер)', + SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_NUMB, + SCOND_FROM => 'BYEV_LINKED_RN', + BFILTER => true, + BVISIBLE => false); + PKG_P8PANELS_VISUAL.TDG_ADD_COL_DEF(RDATA_GRID => RDG, + SNAME => 'SSENDER', + SCAPTION => 'Наименование исполнителя', + SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_STR, + BVISIBLE => false); + /* Формируем блок запроса свойств документа и коллекций наименований/типов */ + UTL_CLNEVENTS_DP_QUERY_BLOCK(NCOMPANY => NCOMPANY, + NCLNEVNTYPES => NCLNEVNTYPES, + NQUERY_POS => 0, + CSQL => CDP_QUERY, + TDP_NAMES => TDP_NAMES, + TDP_TYPES => TDP_TYPES); + /* Если есть свойства документа */ + if (PKG_CONTVALLOC1S.COUNT_(RCONTAINER => TDP_NAMES) <> 0) then + /* Добавляем колонки по свойствам документа */ + RDG_FILL_BY_TDP(RDG => RDG, + RDG_ROW => RDG_ROW, + TDP_NAMES => TDP_NAMES, + TDP_TYPES => TDP_TYPES, + NLAST_NUMB => PKG_CONTVALLOC1S.COUNT_(RCONTAINER => TDP_NAMES), + NACTION => 0); + end if; + /* Обходим данные */ + begin + /* Добавляем подсказку совместимости */ + CSQL := PKG_SQL_BUILD.COMPATIBLE(SSQL => CSQL); + /* Формируем запрос */ + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => 'select T.RN NRN,'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' T.CRN NCRN,'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => PKG_SQL_BUILD.TRIM_() || '(T.EVENT_PREF) SEVPREF,'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => PKG_SQL_BUILD.TRIM_() || '(T.EVENT_NUMB) SEVNUMB,'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' T.EVENT_TYPE NEVTYPE,'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' (select CET.EVNTYPE_CODE'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' from CLNEVNTYPES CET'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' where CET.RN = T.EVENT_TYPE'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' and CET.COMPANY = T.COMPANY) SEVTYPE_CODE,'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' T.REG_DATE DREG_DATE,'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' T.CHANGE_DATE DCHANGE_DATE,'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' T.EXPIRE_DATE DEXPIRE_DATE,'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' T.PLAN_DATE DPLAN_DATE,'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' T.INIT_PERSON NINIT_PERSON,'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' (select P.CODE'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' from CLNPERSONS P'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' where P.RN = T.INIT_PERSON'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' and P.COMPANY = T.COMPANY) SINIT_PERSON,'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' T.EVENT_STAT NEVSTAT,'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' (select CES.EVNSTAT_NAME'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' from CLNEVNSTATS CES'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' where CES.RN = (select CETS.EVENT_STATUS'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' from CLNEVNTYPSTS CETS'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' where CETS.RN = T.EVENT_STAT'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' and CETS.COMPANY = T.COMPANY)'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' and CES.COMPANY = T.COMPANY) SEVSTAT_NAME,'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' T.SEND_PERSON NSEND_PERSON,'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' (select P.CODE'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' from CLNPERSONS P'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' where P.RN = T.SEND_PERSON'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' and P.COMPANY = T.COMPANY) SSEND_PERSON,'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' (select P.PERS_AGENT'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' from CLNPERSONS P'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' where P.RN = T.SEND_PERSON'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' and P.COMPANY = T.COMPANY) NAGNLIST_RN,'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' T.SEND_DIVISION NSEND_DIVISION,'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' (select D.CODE'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' from INS_DEPARTMENT D'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' where D.RN = T.SEND_DIVISION'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' and D.COMPANY = T.COMPANY) SSEND_DIVISION,'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' T.SEND_USER_GROUP NSEND_USRGRP,'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' (select UG.CODE'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' from USERGRP UG'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' where UG.RN = T.SEND_USER_GROUP'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' and UG.COMPANY = T.COMPANY) SSEND_USRGRP,'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' T.EVENT_DESCR SEVDESCR,'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' T.CLOSED NCLOSED,'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' T.LINKED_UNIT SLINKED_UNIT,'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' T.LINKED_RN NLINKED_RN,'); + /* Колонка без TRIM, только для корректной сортировки по номеру события */ + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' T.EVENT_PREF || ''-'' || T.EVENT_NUMB SPREF_NUMB'); + /* Добавление блока запроса для свойств документа */ + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => CDP_QUERY); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' from CLNEVENTS T'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' where T.COMPANY = :NCOMPANY'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' and T.RN in (select ID from COND_BROKER_IDSMART where IDENT = :NIDENT)'); + PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' %ORDER_BY%'); + /* Учтём сортировки */ + PKG_P8PANELS_VISUAL.TDG_ORDERS_SET_QUERY(RDATA_GRID => RDG, + RORDERS => RO, + SPATTERN => '%ORDER_BY%', + CSQL => CSQL); + /* Учтём фильтры */ + PKG_P8PANELS_VISUAL.TDG_FILTERS_SET_QUERY(NIDENT => NIDENT, + NCOMPANY => NCOMPANY, + SUNIT => SUNIT_CLNEVENTS, + SPROCEDURE => 'PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_COND', + RDATA_GRID => RDG, + RFILTERS => RF); + /* Разбираем его */ + ICURSOR := PKG_SQL_DML.OPEN_CURSOR(SWHAT => 'SELECT'); + PKG_SQL_DML.PARSE(ICURSOR => ICURSOR, SQUERY => CSQL); + /* Делаем подстановку параметров */ + PKG_SQL_DML.BIND_VARIABLE_NUM(ICURSOR => ICURSOR, SNAME => 'NCOMPANY', NVALUE => NCOMPANY); + PKG_SQL_DML.BIND_VARIABLE_NUM(ICURSOR => ICURSOR, SNAME => 'NIDENT', NVALUE => NIDENT); + /* Описываем структуру записи курсора */ + PKG_SQL_DML.DEFINE_COLUMN_NUM(ICURSOR => ICURSOR, IPOSITION => 1); + PKG_SQL_DML.DEFINE_COLUMN_STR(ICURSOR => ICURSOR, IPOSITION => 2); + PKG_SQL_DML.DEFINE_COLUMN_STR(ICURSOR => ICURSOR, IPOSITION => 3); + PKG_SQL_DML.DEFINE_COLUMN_STR(ICURSOR => ICURSOR, IPOSITION => 4); + PKG_SQL_DML.DEFINE_COLUMN_NUM(ICURSOR => ICURSOR, IPOSITION => 5); + PKG_SQL_DML.DEFINE_COLUMN_STR(ICURSOR => ICURSOR, IPOSITION => 6); + PKG_SQL_DML.DEFINE_COLUMN_DATE(ICURSOR => ICURSOR, IPOSITION => 7); + PKG_SQL_DML.DEFINE_COLUMN_DATE(ICURSOR => ICURSOR, IPOSITION => 8); + PKG_SQL_DML.DEFINE_COLUMN_DATE(ICURSOR => ICURSOR, IPOSITION => 9); + PKG_SQL_DML.DEFINE_COLUMN_DATE(ICURSOR => ICURSOR, IPOSITION => 10); + PKG_SQL_DML.DEFINE_COLUMN_NUM(ICURSOR => ICURSOR, IPOSITION => 11); + PKG_SQL_DML.DEFINE_COLUMN_STR(ICURSOR => ICURSOR, IPOSITION => 12); + PKG_SQL_DML.DEFINE_COLUMN_NUM(ICURSOR => ICURSOR, IPOSITION => 13); + PKG_SQL_DML.DEFINE_COLUMN_STR(ICURSOR => ICURSOR, IPOSITION => 14); + PKG_SQL_DML.DEFINE_COLUMN_NUM(ICURSOR => ICURSOR, IPOSITION => 15); + PKG_SQL_DML.DEFINE_COLUMN_STR(ICURSOR => ICURSOR, IPOSITION => 16); + PKG_SQL_DML.DEFINE_COLUMN_NUM(ICURSOR => ICURSOR, IPOSITION => 17); + PKG_SQL_DML.DEFINE_COLUMN_NUM(ICURSOR => ICURSOR, IPOSITION => 18); + PKG_SQL_DML.DEFINE_COLUMN_STR(ICURSOR => ICURSOR, IPOSITION => 19); + PKG_SQL_DML.DEFINE_COLUMN_NUM(ICURSOR => ICURSOR, IPOSITION => 20); + PKG_SQL_DML.DEFINE_COLUMN_STR(ICURSOR => ICURSOR, IPOSITION => 21); + PKG_SQL_DML.DEFINE_COLUMN_STR(ICURSOR => ICURSOR, IPOSITION => 22); + PKG_SQL_DML.DEFINE_COLUMN_NUM(ICURSOR => ICURSOR, IPOSITION => 23); + PKG_SQL_DML.DEFINE_COLUMN_STR(ICURSOR => ICURSOR, IPOSITION => 24); + PKG_SQL_DML.DEFINE_COLUMN_NUM(ICURSOR => ICURSOR, IPOSITION => 25); + PKG_SQL_DML.DEFINE_COLUMN_STR(ICURSOR => ICURSOR, IPOSITION => 26); + /* Если есть свойства документа */ + if (PKG_CONTVALLOC1S.COUNT_(RCONTAINER => TDP_NAMES) <> 0) then + /* Добавляем колонок в курсор по свойствам документа */ + RDG_FILL_BY_TDP(RDG => RDG, + RDG_ROW => RDG_ROW, + TDP_NAMES => TDP_NAMES, + TDP_TYPES => TDP_TYPES, + NFIRST_NUMB => RDG.RCOL_DEFS.COUNT - PKG_CONTVALLOC1S.COUNT_(RCONTAINER => TDP_NAMES) - 1, + NLAST_NUMB => RDG.RCOL_DEFS.COUNT - 2, + ICURSOR => ICURSOR, + NACTION => 2); + end if; + /* Делаем выборку */ + if (PKG_SQL_DML.EXECUTE(ICURSOR => ICURSOR) = 0) then + null; + end if; + /* Обходим выбранные записи */ + while (PKG_SQL_DML.FETCH_ROWS(ICURSOR => ICURSOR) > 0) + loop + /* Считываем рег. номер события */ + PKG_SQL_DML.COLUMN_VALUE_NUM(ICURSOR => ICURSOR, IPOSITION => 1, NVALUE => NCLNEVENTS); + /* Считываем статус события */ + PKG_SQL_DML.COLUMN_VALUE_STR(ICURSOR => ICURSOR, IPOSITION => 3, SVALUE => SDOC_PREF); + PKG_SQL_DML.COLUMN_VALUE_STR(ICURSOR => ICURSOR, IPOSITION => 4, SVALUE => SDOC_NUMB); + PKG_SQL_DML.COLUMN_VALUE_STR(ICURSOR => ICURSOR, IPOSITION => 14, SVALUE => SEVSTAT); + /* Считываем наименование исполнителя */ + SSENDER := UTL_CLNEVENTS_SENDER_GET(NCLNEVENTS => NCLNEVENTS); + /* Добавляем колонки с данными */ + RDG_ROW := PKG_P8PANELS_VISUAL.TDG_ROW_MAKE(SGROUP => SEVSTAT); + /* Заполняем колонки */ + PKG_P8PANELS_VISUAL.TDG_ROW_ADD_COL(RROW => RDG_ROW, SNAME => 'NRN', NVALUE => NCLNEVENTS); + PKG_P8PANELS_VISUAL.TDG_ROW_ADD_CUR_COLS(RROW => RDG_ROW, SNAME => 'NCRN', ICURSOR => ICURSOR, NPOSITION => 2); + PKG_P8PANELS_VISUAL.TDG_ROW_ADD_COL(RROW => RDG_ROW, SNAME => 'SEVPREF', SVALUE => SDOC_PREF); + PKG_P8PANELS_VISUAL.TDG_ROW_ADD_COL(RROW => RDG_ROW, SNAME => 'SEVNUMB', SVALUE => SDOC_NUMB); + PKG_P8PANELS_VISUAL.TDG_ROW_ADD_COL(RROW => RDG_ROW, + SNAME => 'SPREF_NUMB', + SVALUE => SDOC_PREF || '-' || SDOC_NUMB); + PKG_P8PANELS_VISUAL.TDG_ROW_ADD_CUR_COLN(RROW => RDG_ROW, + SNAME => 'NEVTYPE', + ICURSOR => ICURSOR, + NPOSITION => 5); + PKG_P8PANELS_VISUAL.TDG_ROW_ADD_CUR_COLS(RROW => RDG_ROW, + SNAME => 'SEVTYPE_CODE', + ICURSOR => ICURSOR, + NPOSITION => 6); + PKG_P8PANELS_VISUAL.TDG_ROW_ADD_CUR_COLD(RROW => RDG_ROW, + SNAME => 'DREG_DATE', + ICURSOR => ICURSOR, + NPOSITION => 7); + PKG_P8PANELS_VISUAL.TDG_ROW_ADD_CUR_COLD(RROW => RDG_ROW, + SNAME => 'DCHANGE_DATE', + ICURSOR => ICURSOR, + NPOSITION => 8); + PKG_P8PANELS_VISUAL.TDG_ROW_ADD_CUR_COLD(RROW => RDG_ROW, + SNAME => 'DEXPIRE_DATE', + ICURSOR => ICURSOR, + NPOSITION => 9); + PKG_P8PANELS_VISUAL.TDG_ROW_ADD_CUR_COLD(RROW => RDG_ROW, + SNAME => 'DPLAN_DATE', + ICURSOR => ICURSOR, + NPOSITION => 10); + PKG_P8PANELS_VISUAL.TDG_ROW_ADD_CUR_COLN(RROW => RDG_ROW, + SNAME => 'NINIT_PERSON', + ICURSOR => ICURSOR, + NPOSITION => 11); + PKG_P8PANELS_VISUAL.TDG_ROW_ADD_CUR_COLS(RROW => RDG_ROW, + SNAME => 'SINIT_PERSON', + ICURSOR => ICURSOR, + NPOSITION => 12); + PKG_P8PANELS_VISUAL.TDG_ROW_ADD_CUR_COLN(RROW => RDG_ROW, + SNAME => 'NEVSTAT', + ICURSOR => ICURSOR, + NPOSITION => 13); + PKG_P8PANELS_VISUAL.TDG_ROW_ADD_COL(RROW => RDG_ROW, SNAME => 'SEVSTAT_NAME', SVALUE => SEVSTAT); + PKG_P8PANELS_VISUAL.TDG_ROW_ADD_CUR_COLN(RROW => RDG_ROW, + SNAME => 'NSEND_PERSON', + ICURSOR => ICURSOR, + NPOSITION => 15); + PKG_P8PANELS_VISUAL.TDG_ROW_ADD_CUR_COLS(RROW => RDG_ROW, + SNAME => 'SSEND_PERSON', + ICURSOR => ICURSOR, + NPOSITION => 16); + PKG_P8PANELS_VISUAL.TDG_ROW_ADD_CUR_COLN(RROW => RDG_ROW, + SNAME => 'NAGNLIST_RN', + ICURSOR => ICURSOR, + NPOSITION => 17); + PKG_P8PANELS_VISUAL.TDG_ROW_ADD_CUR_COLN(RROW => RDG_ROW, + SNAME => 'NSEND_DIVISION', + ICURSOR => ICURSOR, + NPOSITION => 18); + PKG_P8PANELS_VISUAL.TDG_ROW_ADD_CUR_COLS(RROW => RDG_ROW, + SNAME => 'SSEND_DIVISION', + ICURSOR => ICURSOR, + NPOSITION => 19); + PKG_P8PANELS_VISUAL.TDG_ROW_ADD_CUR_COLN(RROW => RDG_ROW, + SNAME => 'NSEND_USRGRP', + ICURSOR => ICURSOR, + NPOSITION => 20); + PKG_P8PANELS_VISUAL.TDG_ROW_ADD_CUR_COLS(RROW => RDG_ROW, + SNAME => 'SSEND_USRGRP', + ICURSOR => ICURSOR, + NPOSITION => 21); + PKG_P8PANELS_VISUAL.TDG_ROW_ADD_CUR_COLS(RROW => RDG_ROW, + SNAME => 'SEVDESCR', + ICURSOR => ICURSOR, + NPOSITION => 22); + PKG_P8PANELS_VISUAL.TDG_ROW_ADD_CUR_COLN(RROW => RDG_ROW, + SNAME => 'NCLOSED', + ICURSOR => ICURSOR, + NPOSITION => 23); + PKG_P8PANELS_VISUAL.TDG_ROW_ADD_CUR_COLS(RROW => RDG_ROW, + SNAME => 'SLINKED_UNIT', + ICURSOR => ICURSOR, + NPOSITION => 24); + PKG_P8PANELS_VISUAL.TDG_ROW_ADD_CUR_COLN(RROW => RDG_ROW, + SNAME => 'NLINKED_RN', + ICURSOR => ICURSOR, + NPOSITION => 25); + PKG_P8PANELS_VISUAL.TDG_ROW_ADD_COL(RROW => RDG_ROW, SNAME => 'SSENDER', SVALUE => SSENDER); + /* Если есть свойства документа */ + if (PKG_CONTVALLOC1S.COUNT_(RCONTAINER => TDP_NAMES) <> 0) then + /* Заполняем строку по свойствам документа */ + RDG_FILL_BY_TDP(RDG => RDG, + RDG_ROW => RDG_ROW, + TDP_NAMES => TDP_NAMES, + TDP_TYPES => TDP_TYPES, + NFIRST_NUMB => RDG.RCOL_DEFS.COUNT - PKG_CONTVALLOC1S.COUNT_(RCONTAINER => TDP_NAMES) - 1, + NLAST_NUMB => RDG.RCOL_DEFS.COUNT - 2, + ICURSOR => ICURSOR, + NACTION => 1); + end if; + /* Добавляем строку в таблицу */ + PKG_P8PANELS_VISUAL.TDG_ADD_ROW(RDATA_GRID => RDG, RROW => RDG_ROW); + end loop; + /* Освобождаем курсор */ + PKG_SQL_DML.CLOSE_CURSOR(ICURSOR => ICURSOR); + exception + when others then + PKG_SQL_DML.CLOSE_CURSOR(ICURSOR => ICURSOR); + raise; + end; + /* Сериализуем описание */ + COUT := PKG_P8PANELS_VISUAL.TDG_TO_XML(RDATA_GRID => RDG, NINCLUDE_DEF => 0); + /* Очистка коллекций */ + PKG_CONTVALLOC1S.PURGE(RCONTAINER => TDP_NAMES); + PKG_CONTVALLOC1S.PURGE(RCONTAINER => TDP_TYPES); + end CLNEVENTS_DG_GET; + + /* Считывание контрагентов событий с наличием изображения */ + procedure CLNEVENTS_AGENTS_WIMG_GET + ( + NIDENT in number, -- Идентификатор отобранных записей событий + COUT out clob -- Информация о контрагентах с наличием изображения + ) + is + RANGLIST AGNLIST%rowtype; -- Запись контрагента + + /* Считывание записи контрагента */ + function GET_AGNLIST_ID + ( + NRN in number -- Рег. номер контрагента + ) return AGNLIST%rowtype -- Запись контрагента + is + RRESULT AGNLIST%rowtype; -- Запись контрагента + begin + /* Считываем запись */ + begin + select T.* into RRESULT from AGNLIST T where T.RN = NRN; + exception + when others then + RRESULT := null; + end; + /* Возвращаем результат */ + return RRESULT; + end GET_AGNLIST_ID; + begin + /* Начинаем формирование XML */ + PKG_XFAST.PROLOGUE(ITYPE => PKG_XFAST.CONTENT_); + /* Обходим контрагентов событий, у которых есть изображение */ + for REC in (select COALESCE(SC_A.RN, SP_A.RN) RN + from CLNEVENTS T, + CLNCLIENTS SC, + AGNLIST SC_A, + CLNPERSONS SP, + AGNLIST SP_A + where T.RN in (select ID from COND_BROKER_IDSMART where IDENT = NIDENT) + and ((T.SEND_CLIENT is not null) or (T.SEND_PERSON is not null)) + and T.SEND_CLIENT = SC.RN(+) + and SC.CLIENT_AGENT = SC_A.RN(+) + and T.SEND_PERSON = SP.RN(+) + and SP.PERS_AGENT = SP_A.RN(+) + and ((DBMS_LOB.GETLENGTH(SC_A.IMAGE) <> 0) or (DBMS_LOB.GETLENGTH(SP_A.IMAGE) <> 0)) + group by COALESCE(SC_A.RN, SP_A.RN)) + loop + /* Считываем запись контрагента */ + RANGLIST := GET_AGNLIST_ID(NRN => REC.RN); + /* Если запись считана */ + if (RANGLIST.RN is not null) then + /* Добавляем информацию о контрагенте */ + PKG_XFAST.DOWN_NODE(SNAME => 'XAGENTS'); + /* Мнемокод контрагента */ + PKG_XFAST.DOWN_NODE(SNAME => 'SAGNABBR'); + PKG_XFAST.VALUE(SVALUE => RANGLIST.AGNABBR); + PKG_XFAST.UP(); + /* Изображение контрагента */ + PKG_XFAST.DOWN_NODE(SNAME => 'BIMAGE'); + PKG_XFAST.VALUE(LBVALUE => RANGLIST.IMAGE); + PKG_XFAST.UP(); + PKG_XFAST.UP(); + end if; + end loop; + /* Сериализуем */ + COUT := PKG_XFAST.SERIALIZE_TO_CLOB(); + /* Завершаем формирование XML */ + PKG_XFAST.EPILOGUE(); + end CLNEVENTS_AGENTS_WIMG_GET; + + /* Загрузка информации о событиях */ + procedure CLNEVENTS_LOAD + ( + CFILTERS in clob, -- Фильтры + CORDERS in clob, -- Сортировки + NINCLUDE_ACCOUNTS in number := 1, -- Включать информацию о контрагентах с изображением (0 - нет, 1 - да) + COUT out clob -- Сериализованная информация о событиях + ) + is + NIDENT PKG_STD.TREF; -- Идентификатор отбора + CCLNEVENTS clob; -- Информация о событиях + CAGNLIST_WITH_IMAGE clob; -- Информация о контрагентах с изображением + begin + /* Генерируем идентификатор */ + NIDENT := GEN_IDENT(); + /* Считываем данные о событиях */ + CLNEVENTS_DG_GET(CFILTERS => CFILTERS, + CORDERS => CORDERS, + COUT => CCLNEVENTS, + NIDENT => NIDENT); + /* Если требуется включать информацию о контрагентах */ + if (NINCLUDE_ACCOUNTS = 1) then + /* Считываем информацию о контрагентах с наличием изображения */ + CLNEVENTS_AGENTS_WIMG_GET(NIDENT => NIDENT, COUT => CAGNLIST_WITH_IMAGE); + end if; + /* Начинаем формирование XML */ + PKG_XFAST.PROLOGUE(ITYPE => PKG_XFAST.CONTENT_); + /* Открываем корень */ + PKG_XFAST.DOWN_NODE(SNAME => 'XDATA'); + /* Открываем события */ + PKG_XFAST.DOWN_NODE(SNAME => 'XCLNEVENTS'); + /* Описываем события */ + PKG_XFAST.VALUE_XML(LCVALUE => CCLNEVENTS); + /* Закрываем события */ + PKG_XFAST.UP(); + /* Если требуется включать информацию о контрагентах */ + if (NINCLUDE_ACCOUNTS = 1) then + /* Открываем информацию о контрагентах с изображением */ + PKG_XFAST.DOWN_NODE(SNAME => 'XAGENTS_WITH_IMG'); + /* Описываем контрагентов */ + PKG_XFAST.VALUE_XML(LCVALUE => CAGNLIST_WITH_IMAGE); + /* Закрываем контрагентов */ + PKG_XFAST.UP(); + end if; + /* Закрываем корень */ + PKG_XFAST.UP(); + /* Сериализуем */ + COUT := PKG_XFAST.SERIALIZE_TO_CLOB(); + /* Завершаем формирование XML */ + PKG_XFAST.EPILOGUE(); + exception + when others then + /* Завершаем формирование XML */ + PKG_XFAST.EPILOGUE(); + /* Вернем ошибку */ + PKG_STATE.DIAGNOSTICS_STACKED(); + P_EXCEPTION(0, PKG_STATE.SQL_ERRM()); + end CLNEVENTS_LOAD; + + /* Выбор события по рег. номеру */ + procedure CLNEVENTS_SELECT + ( + NCLNEVENTS in number, -- Рег. номер события + NIDENT out number -- Идентификатор буфера подобранных (списка отмеченных записей, null - не найдено) + ) + is + NCOMPANY PKG_STD.TREF := GET_SESSION_COMPANY(); -- Рег. номер организации + NSELECTLIST PKG_STD.TREF; -- Рег. номер добавленной записи буфера подобранных + NCRN PKG_STD.TREF; -- Рег. номер каталога + + /* Считывание каталога события */ + function CLNEVENTS_CRN_GET + ( + NCLNEVENTS in number -- Рег. номер события + ) return number -- Рег. номер каталога события + is + NRESULT PKG_STD.TREF; -- Рег. номер каталога события + begin + /* Считываем каталог события */ + begin + select E.CRN into NRESULT from CLNEVENTS E where E.RN = NCLNEVENTS; + exception + when others then + NCRN := null; + end; + /* Возвращаем результат */ + return NRESULT; + end CLNEVENTS_CRN_GET; + begin + /* Считываем рег. номер каталога события */ + NCRN := CLNEVENTS_CRN_GET(NCLNEVENTS => NCLNEVENTS); + /* Если каталог считан */ + if (NCRN is not null) then + /* Сформируем идентификатор буфера */ + if (NIDENT is null) then + NIDENT := GEN_IDENT(); + end if; + /* Добавим подобранное в список отмеченных записей */ + P_SELECTLIST_BASE_INSERT(NIDENT => NIDENT, + NCOMPANY => NCOMPANY, + NDOCUMENT => NCLNEVENTS, + SUNITCODE => SUNIT_CLNEVENTS, + SACTIONCODE => null, + NCRN => NCRN, + NDOCUMENT1 => null, + SUNITCODE1 => null, + SACTIONCODE1 => null, + NRN => NSELECTLIST); + end if; + end CLNEVENTS_SELECT; + + /* Считывание следующего номера события */ + procedure CLNEVENTS_NEXTNUMB_GET + ( + SPREFIX in varchar2, -- Префикс события + SEVENT_NUMB out varchar2 -- Номер события + ) + is + NCOMPANY PKG_STD.TREF := GET_SESSION_COMPANY(); -- Организация сеанса + begin + /* Считываем следующий номер */ + SEVENT_NUMB := GET_CLNEVENTS_NEXTNUMB(NCOMPANY => NCOMPANY, SPREF => SPREFIX); + end CLNEVENTS_NEXTNUMB_GET; + + /* Считывание параметров события */ + procedure CLNEVENTS_GET + ( + NCLNEVENTS in number, -- Рег. номер события + COUT out clob -- Данные о записи события + ) + is + NCOMPANY PKG_STD.TREF := GET_SESSION_COMPANY(); -- Организация сеанса + RCLNEVENTS CLNEVENTS%rowtype; -- Запись события + SCRN PKG_STD.TSTRING; -- Наименование каталога + STYPE PKG_STD.TSTRING; -- Мнемокод типа события + SSTATUS PKG_STD.TSTRING; -- Мнемокод статуса типа события + SINIT_PERSON PKG_STD.TSTRING; -- Мнемокод инициатора сотрудника + SCLIENT_CLIENT PKG_STD.TSTRING; -- Мнемокод клиента (организации) + SCLIENT_PERSON PKG_STD.TSTRING; -- Мнемокод клиента (сотрудника) + SSEND_CLIENT PKG_STD.TSTRING; -- Мнемокод направления (организация) + SSEND_DIVISION PKG_STD.TSTRING; -- Мнемокод направления (подразделение) + SSEND_POST PKG_STD.TSTRING; -- Мнемокод направления (должность) + SSEND_PERFORM PKG_STD.TSTRING; -- Мнемокод направления (должность в подразделении) + SSEND_PERSON PKG_STD.TSTRING; -- Мнемокод направления (сотрудник) + SSEND_STAFFGRP PKG_STD.TSTRING; -- Мнемокод направления (нештатная структура) + SSEND_USER_GROUP PKG_STD.TSTRING; -- Мнемокод направления (группа пользователей) + SSEND_USER_NAME PKG_STD.TSTRING; -- Мнемокод направления (группа пользователь) + SDP_FORMATTED_ID PKG_STD.TSTRING; -- Отформатированный ID свойства документа + begin + /* Считываем запись события */ + RCLNEVENTS := UTL_CLNEVENTS_GET(NCOMPANY => NCOMPANY, NCLNEVENTS => NCLNEVENTS); + /* Считываем наименование каталога */ + FIND_ACATALOG_RN(NFLAG_SMART => 0, + NCOMPANY => NCOMPANY, + NVERSION => null, + SUNITCODE => SUNIT_CLNEVENTS, + NRN => RCLNEVENTS.CRN, + SNAME => SCRN); + /* Считываем мнемокоды связанных записей */ + UTL_CLNEVENTS_JOINS_GET(NCLNEVENTS => RCLNEVENTS.RN, + STYPE => STYPE, + SSTATUS => SSTATUS, + SINIT_PERSON => SINIT_PERSON, + SCLIENT_CLIENT => SCLIENT_CLIENT, + SCLIENT_PERSON => SCLIENT_PERSON, + SSEND_CLIENT => SSEND_CLIENT, + SSEND_DIVISION => SSEND_DIVISION, + SSEND_POST => SSEND_POST, + SSEND_PERFORM => SSEND_PERFORM, + SSEND_PERSON => SSEND_PERSON, + SSEND_STAFFGRP => SSEND_STAFFGRP, + SSEND_USER_GROUP => SSEND_USER_GROUP, + SSEND_USER_NAME => SSEND_USER_NAME); + /* Формируем данные на выход */ + PKG_XFAST.PROLOGUE(ITYPE => PKG_XFAST.CONTENT_); + PKG_XFAST.DOWN_NODE(SNAME => 'XDATA'); + PKG_XFAST.DOWN_NODE(SNAME => 'XEVENT'); + PKG_XFAST.ATTR(SNAME => 'SCRN', SVALUE => SCRN); + PKG_XFAST.ATTR(SNAME => 'SPREF', SVALUE => RCLNEVENTS.EVENT_PREF); + PKG_XFAST.ATTR(SNAME => 'SNUMB', SVALUE => RCLNEVENTS.EVENT_NUMB); + PKG_XFAST.ATTR(SNAME => 'STYPE', SVALUE => STYPE); + PKG_XFAST.ATTR(SNAME => 'SSTATUS', SVALUE => SSTATUS); + PKG_XFAST.ATTR(SNAME => 'SDESCRIPTION', SVALUE => RCLNEVENTS.EVENT_DESCR); + PKG_XFAST.ATTR(SNAME => 'SCLIENT_CLIENT', SVALUE => SCLIENT_CLIENT); + PKG_XFAST.ATTR(SNAME => 'SCLIENT_PERSON', SVALUE => SCLIENT_PERSON); + PKG_XFAST.ATTR(SNAME => 'SPLAN_DATE', SVALUE => TO_CHAR(RCLNEVENTS.PLAN_DATE, SDATE_PATTERN_HH_MI)); + PKG_XFAST.ATTR(SNAME => 'SINIT_PERSON', SVALUE => SINIT_PERSON); + PKG_XFAST.ATTR(SNAME => 'SINIT_AUTHID', SVALUE => RCLNEVENTS.INIT_AUTHID); + PKG_XFAST.ATTR(SNAME => 'SREASON', SVALUE => RCLNEVENTS.REASON); + PKG_XFAST.ATTR(SNAME => 'SSEND_CLIENT', SVALUE => SSEND_CLIENT); + PKG_XFAST.ATTR(SNAME => 'SSEND_DIVISION', SVALUE => SSEND_DIVISION); + PKG_XFAST.ATTR(SNAME => 'SSEND_POST', SVALUE => SSEND_POST); + PKG_XFAST.ATTR(SNAME => 'SSEND_PERFORM', SVALUE => SSEND_PERFORM); + PKG_XFAST.ATTR(SNAME => 'SSEND_PERSON', SVALUE => SSEND_PERSON); + PKG_XFAST.ATTR(SNAME => 'SSEND_STAFFGRP', SVALUE => SSEND_STAFFGRP); + PKG_XFAST.ATTR(SNAME => 'SSEND_USER_GROUP', SVALUE => SSEND_USER_GROUP); + PKG_XFAST.ATTR(SNAME => 'SSEND_USER_NAME', SVALUE => SSEND_USER_NAME); + /* Цикл добавления свойств документа события */ + for REC in (select DPV.*, + DP.FORMAT, + DP.DATA_SUBTYPE + from CLEVTPROPS CEP, + CLNEVNTYPES CET, + DOCS_PROPS_VALS DPV, + DOCS_PROPS DP + where CEP.PRN = CET.RN + and CET.EVNTYPE_CODE = STYPE + and CEP.COMPANY = NCOMPANY + and CEP.PROPERTY = DPV.DOCS_PROP_RN(+) + and CEP.PROPERTY = DP.RN(+) + and DPV.UNIT_RN = NCLNEVENTS) + loop + /* Считываем отформатированный ID свойства документа */ + SDP_FORMATTED_ID := UTL_DP_FORMATTED_ID_GET(NTYPE => REC.FORMAT, NRN => REC.DOCS_PROP_RN); + /* Исходим от типа свойства документа */ + case + /* Строка */ + when (REC.FORMAT = 0) then + PKG_XFAST.ATTR(SNAME => SDP_FORMATTED_ID, SVALUE => REC.STR_VALUE); + /* Число */ + when ((REC.FORMAT = 1) or (REC.FORMAT = 3)) then + PKG_XFAST.ATTR(SNAME => SDP_FORMATTED_ID, NVALUE => REC.NUM_VALUE); + /* Дата */ + else + PKG_XFAST.ATTR(SNAME => SDP_FORMATTED_ID, DVALUE => REC.DATE_VALUE, IFORMAT => REC.DATA_SUBTYPE); + end case; + end loop; + PKG_XFAST.UP(); + PKG_XFAST.UP(); + /* Сериализуем в CLOB */ + COUT := PKG_XFAST.SERIALIZE_TO_CLOB(); + PKG_XFAST.EPILOGUE(); + exception + when others then + /* Завершаем формирование XML */ + PKG_XFAST.EPILOGUE(); + /* Вернем ошибку */ + PKG_STATE.DIAGNOSTICS_STACKED(); + P_EXCEPTION(0, PKG_STATE.SQL_ERRM()); + end CLNEVENTS_GET; + + /* Инициализация параметров события */ + procedure CLNEVENTS_INIT + ( + SEVENT_TYPE in varchar2, -- Мнемокод типа события + SPREF out varchar2, -- Префикс события + SNUMB out varchar2, -- Номер события + SINIT_PERSON out varchar2, -- Сотрудник - инициатор + SINIT_AUTHNAME out varchar2 -- Пользователь - инициатор + ) + is + NCOMPANY PKG_STD.TREF := GET_SESSION_COMPANY(); -- Организация сеанса + SOWNER_AGENT_RN PKG_STD.TSTRING; -- Рег. номер работодателя сотрудника + STAB_PREF PKG_STD.TSTRING; -- Префикс табельного номера сотрудника + STAB_NUMB PKG_STD.TSTRING; -- Таблеьный номер сотрудника + + /* Считывание префикса типа события */ + function CLNEVNTYPES_PREF_GET + ( + NCOMPANY in number, -- Рег. номер организации + SEVENT_TYPE in varchar2 -- Мнемокод типа события + ) return varchar2 -- Префикс типа события + is + SRESULT PKG_STD.TSTRING; -- Префикс типа события + begin + /* Считываем префикс типа события */ + begin + select T.EVENT_PREF + into SRESULT + from CLNEVNTYPES T + where T.EVNTYPE_CODE = SEVENT_TYPE + and T.COMPANY = NCOMPANY; + exception + when NO_DATA_FOUND then + SRESULT := null; + end; + /* Возвращаем результат */ + return SRESULT; + end CLNEVNTYPES_PREF_GET; + begin + /* Считываем префикс из параметра */ + SPREF := GET_OPTIONS_STR(SCODE => 'EventPrefix', NCOMP_VERS => NCOMPANY); + /* Если префикс не указан в параметре и задан тип события */ + if ((SPREF is null) and (SEVENT_TYPE is not null)) then + /* Считываем префикс типа события */ + SPREF := CLNEVNTYPES_PREF_GET(NCOMPANY => NCOMPANY, SEVENT_TYPE => SEVENT_TYPE); + end if; + /* Если префикс определен */ + if (SPREF is not null) then + /* Считываем номер */ + SNUMB := GET_CLNEVENTS_NEXTNUMB(NCOMPANY => NCOMPANY, SPREF => SPREF); + end if; + /* Считываем сотрудника текущего пользователя */ + FIND_PERSON_AUTHID(SPERSON => SINIT_PERSON, + SOWNER_AGENT => SOWNER_AGENT_RN, + STAB_PREF => STAB_PREF, + STAB_NUMB => STAB_NUMB); + /* Считываем имя пользователя */ + FIND_USERLIST_BY_AUTHID(NFLAG_SMART => 1, SAUTHID => UTILIZER, SNAME => SINIT_AUTHNAME); + end CLNEVENTS_INIT; + + /* Добавление значений свойств документа */ + procedure CLNEVENTS_PROPS_INSERT + ( + NCLNEVENTS in number, -- Рег. номер события + SEVENT_TYPE in varchar2, -- Мнемокод типа события + CPROPS in clob -- Свойства документа + ) + is + NCOMPANY PKG_STD.TREF := GET_SESSION_COMPANY(); -- Рег. номер организации + NDP_VER PKG_STD.TREF; -- Версия раздела "Свойства документов" + RDOC PKG_XPATH.TDOCUMENT; -- Документ XML + RNODE_ROOT PKG_XPATH.TNODE; -- Корневой узел + RNODE_PROPS PKG_XPATH.TNODE; -- Узел "props" + RNODE_PROP PKG_XPATH.TNODE; -- Узел свойства (в формате "_<РЕГ_НОМЕР>") + SPROP_ID PKG_STD.TSTRING; -- Код свойства документа из XML (в формате "_<РЕГ_НОМЕР>") + SPROP_CODE PKG_STD.TSTRING; -- Мнемокод свойства документа + SSTR_VALUE PKG_STD.TSTRING := null; -- Строковое значение + NNUM_VALUE PKG_STD.TLNUMBER := null; -- Числовое значение + DDATE_VALUE PKG_STD.TLDATE := null; -- Дата + begin + /* Считываем версию организации */ + FIND_VERSION_BY_COMPANY(NCOMPANY => NCOMPANY, SUNITCODE => SUNIT_DP, NVERSION => NDP_VER); + /* Загрузка XML */ + RDOC := PKG_XPATH.PARSE_FROM_CLOB(LCXML => BLOB2CLOB(LBDATA => BASE64_DECODE(LCSRCE => CPROPS), + SCHARSET => PKG_CHARSET.CHARSET_UTF_())); + /* Разбор XML */ + RNODE_ROOT := PKG_XPATH.ROOT_NODE(RDOCUMENT => RDOC); + RNODE_PROPS := PKG_XPATH.SINGLE_NODE(RPARENT_NODE => RNODE_ROOT, SPATTERN => 'props'); + /* Если найдено событие */ + if (PKG_DOCS_PROPS_VALS.PROLOGUE(SUNITCODE => SUNIT_CLNEVENTS, NDOCUMENT => NCLNEVENTS)) then + /* Цикл по свойствам документа типа события */ + for REC in (select CEP.RN, + CEP.PROPERTY, + DP.FORMAT, + DP.NUM_WIDTH, + DP.NUM_PRECISION, + DP.STR_WIDTH + from CLEVTPROPS CEP, + CLNEVNTYPES CET, + DOCS_PROPS DP + where CEP.PRN = CET.RN + and CET.EVNTYPE_CODE = SEVENT_TYPE + and CEP.COMPANY = NCOMPANY + and CEP.PROPERTY = DP.RN(+)) + loop + /* Считываем код свойства */ + SPROP_ID := UTL_DP_FORMATTED_ID_GET(NTYPE => REC.FORMAT, NRN => REC.PROPERTY); + SPROP_CODE := GET_DOCS_PROPS_CODE_ID(NFLAG_SMART => 0, NPROP_ID => REC.PROPERTY); + /* Если в XML найдено новое значение свойства */ + if (PKG_XPATH.IS_NULL(RNODE => PKG_XPATH.SINGLE_NODE(RPARENT_NODE => RNODE_PROPS, SPATTERN => SPROP_ID)) = + false) then + /* Считываем узел свойства документа */ + RNODE_PROP := PKG_XPATH.SINGLE_NODE(RPARENT_NODE => RNODE_PROPS, SPATTERN => SPROP_ID); + /* Исходим от типа свойства документа */ + case REC.FORMAT + /* Строка */ + when 0 then + SSTR_VALUE := PKG_XPATH.VALUE(RNODE => RNODE_PROP); + /* Проверка длины строки */ + if (LENGTH(SSTR_VALUE) > REC.STR_WIDTH) then + P_EXCEPTION(0, + 'Недопустимый формат значения для свойства "%s" (ожидалась строка длиной "%s" символов).', + SPROP_CODE, + TO_CHAR(REC.STR_WIDTH)); + end if; + /* Число */ + when 1 then + NNUM_VALUE := PKG_XPATH.VALUE_NUM(RNODE => RNODE_PROP); + /* Проверка длины и точности числа */ + if ((LENGTH(TO_CHAR(TRUNC(ABS(NNUM_VALUE)))) > (REC.NUM_WIDTH - REC.NUM_PRECISION)) or + (ROUND(NNUM_VALUE, REC.NUM_PRECISION) <> NNUM_VALUE)) then + P_EXCEPTION(0, + 'Недопустимый формат значения для свойства "%s" (ожидалось число размерностью "%s" с точностью "%s").', + SPROP_CODE, + TO_CHAR(REC.NUM_WIDTH), + TO_CHAR(REC.NUM_PRECISION)); + end if; + /* Дата */ + when 2 then + begin + DDATE_VALUE := PKG_XPATH.VALUE_DATE(RNODE => RNODE_PROP); + exception + when others then + P_EXCEPTION(0, 'Недопустимый формат даты.'); + end; + /* Время */ + else + NNUM_VALUE := TO_DATE(PKG_XPATH.VALUE(RNODE => RNODE_PROP), 'HH24:MI:SS') - + TO_DATE('00:00:00', 'HH24:MI:SS'); + end case; + end if; + /* Добавление значения свойства */ + PKG_DOCS_PROPS_VALS.APPEND(SPROPERTY => SPROP_CODE, + SSTR_VALUE => SSTR_VALUE, + NNUM_VALUE => NNUM_VALUE, + DDATE_VALUE => DDATE_VALUE); + /* Очищаем значения */ + SSTR_VALUE := null; + NNUM_VALUE := null; + DDATE_VALUE := null; + end loop; + PKG_DOCS_PROPS_VALS.EPILOGUE; + end if; + end CLNEVENTS_PROPS_INSERT; + + /* Добавление события */ + procedure CLNEVENTS_INSERT + ( + SCRN in varchar2, -- Наименование каталога + SPREF in varchar2, -- Префикс события + SNUMB in varchar2, -- Номер события + STYPE in varchar2, -- Тип события + SSTATUS in varchar2, -- Статус события + SPLAN_DATE in varchar2, -- Дата планируемого начала работ + SINIT_PERSON in varchar2, -- Инициатор + SCLIENT_CLIENT in varchar2, -- Клиент-инициатор + SCLIENT_PERSON in varchar2, -- Сотрудник-инициатор + SDESCRIPTION in varchar2, -- Описание + SREASON in varchar2, -- Причина + CPROPS in clob -- Свойства документа + ) + is + NCOMPANY PKG_STD.TREF := GET_SESSION_COMPANY(); -- Организация сеанса + NCRN PKG_STD.TREF; -- Рег. номер каталога + NCLNEVENTS PKG_STD.TREF; -- Рег. номер события + begin + /* Считываем рег. номер каталога */ + FIND_ACATALOG_NAME(NFLAG_SMART => 0, + NCOMPANY => NCOMPANY, + NVERSION => null, + SUNITCODE => SUNIT_CLNEVENTS, + SNAME => SCRN, + NRN => NCRN); + /* Добавляем событие */ + P_CLNEVENTS_INSERT(NCOMPANY => NCOMPANY, + NCRN => NCRN, + SEVENT_PREF => SPREF, + SEVENT_NUMB => SNUMB, + SEVENT_TYPE => STYPE, + SEVENT_STAT => SSTATUS, + DPLAN_DATE => TO_DATE(SPLAN_DATE, SDATE_PATTERN_HH_MI), + SINIT_PERSON => SINIT_PERSON, + SCLIENT_CLIENT => SCLIENT_CLIENT, + SCLIENT_PERSON => SCLIENT_PERSON, + SSEND_CLIENT => null, + SSEND_DIVISION => null, + SSEND_POST => null, + SSEND_PERFORM => null, + SSEND_PERSON => null, + SSEND_STAFFGRP => null, + SSEND_USER_GROUP => null, + SSEND_USER_NAME => null, + SEVENT_DESCR => SDESCRIPTION, + SREASON => SREASON, + NRN => NCLNEVENTS); + /* Добавляем значения свойств документа */ + CLNEVENTS_PROPS_INSERT(NCLNEVENTS => NCLNEVENTS, SEVENT_TYPE => STYPE, CPROPS => CPROPS); + end CLNEVENTS_INSERT; + + /* Исправление события */ + procedure CLNEVENTS_UPDATE + ( + NCLNEVENTS in number, -- Рег. номер события + SCLIENT_CLIENT in varchar2, -- Клиент-инициатор + SCLIENT_PERSON in varchar2, -- Сотрудник-инициатор + SDESCRIPTION in varchar2, -- Описание + CPROPS in clob -- Свойства + ) + is + NCOMPANY PKG_STD.TREF := GET_SESSION_COMPANY(); -- Организация сеанса + RCLNEVENTS CLNEVENTS%rowtype; -- Запись события + STYPE PKG_STD.TSTRING; -- Мнемокод типа события + SSTATUS PKG_STD.TSTRING; -- Мнемокод статуса типа события + SINIT_PERSON PKG_STD.TSTRING; -- Мнемокод инициатора сотрудника + SCLIENT_CLIENT_TMP PKG_STD.TSTRING; -- Мнемокод клиента (организации) (не требуется) + SCLIENT_PERSON_TMP PKG_STD.TSTRING; -- Мнемокод клиента (сотрудника) (не требуется) + SSEND_CLIENT PKG_STD.TSTRING; -- Мнемокод направления (организация) + SSEND_DIVISION PKG_STD.TSTRING; -- Мнемокод направления (подразделение) + SSEND_POST PKG_STD.TSTRING; -- Мнемокод направления (должность) + SSEND_PERFORM PKG_STD.TSTRING; -- Мнемокод направления (должность в подразделении) + SSEND_PERSON PKG_STD.TSTRING; -- Мнемокод направления (сотрудник) + SSEND_STAFFGRP PKG_STD.TSTRING; -- Мнемокод направления (нештатная структура) + SSEND_USER_GROUP PKG_STD.TSTRING; -- Мнемокод направления (группа пользователей) + SSEND_USER_NAME PKG_STD.TSTRING; -- Мнемокод направления (группа пользователь) + NBAN_UPDATE PKG_STD.TNUMBER; -- Признак запрета исправления события в точке маршрута (0 - нет, 1 - да) + begin + /* Считываем запись события */ + RCLNEVENTS := UTL_CLNEVENTS_GET(NCOMPANY => NCOMPANY, NCLNEVENTS => NCLNEVENTS); + /* Считываем мнемокоды связанных записей */ + UTL_CLNEVENTS_JOINS_GET(NCLNEVENTS => RCLNEVENTS.RN, + STYPE => STYPE, + SSTATUS => SSTATUS, + SINIT_PERSON => SINIT_PERSON, + SCLIENT_CLIENT => SCLIENT_CLIENT_TMP, + SCLIENT_PERSON => SCLIENT_PERSON_TMP, + SSEND_CLIENT => SSEND_CLIENT, + SSEND_DIVISION => SSEND_DIVISION, + SSEND_POST => SSEND_POST, + SSEND_PERFORM => SSEND_PERFORM, + SSEND_PERSON => SSEND_PERSON, + SSEND_STAFFGRP => SSEND_STAFFGRP, + SSEND_USER_GROUP => SSEND_USER_GROUP, + SSEND_USER_NAME => SSEND_USER_NAME); + /* Проверка возможности редактирования события в текущем статусе */ + NBAN_UPDATE := UTL_CLNEVENTS_BAN_UPDATE_GET(NCOMPANY => NCOMPANY, NCLNEVENTS => NCLNEVENTS); + /* Если редактирование запрещено */ + if (NBAN_UPDATE = 1) then + /* Формируем исключение */ + P_EXCEPTION(0, 'События в статусе "%s" запрещено редактировать.', SSTATUS); + end if; + /* Исправляем событие */ + P_CLNEVENTS_UPDATE(NCOMPANY => NCOMPANY, + NRN => NCLNEVENTS, + NREMOTE_ACCESS => null, + SCHANGE_DATE => STAMP2S(RCLNEVENTS.CHANGE_DATE), + SEVENT_TYPE => STYPE, + SEVENT_STAT => SSTATUS, + SCLIENT_CLIENT => SCLIENT_CLIENT, + SCLIENT_PERSON => SCLIENT_PERSON, + SSEND_CLIENT => SSEND_CLIENT, + SSEND_DIVISION => SSEND_DIVISION, + SSEND_POST => SSEND_POST, + SSEND_PERFORM => SSEND_PERFORM, + SSEND_PERSON => SSEND_PERSON, + SSEND_STAFFGRP => SSEND_STAFFGRP, + SSEND_USER_GROUP => SSEND_USER_GROUP, + SSEND_USER_NAME => SSEND_USER_NAME, + SEVENT_DESCR => SDESCRIPTION); + /* Обновляем значения свойств документа */ + CLNEVENTS_PROPS_INSERT(NCLNEVENTS => NCLNEVENTS, SEVENT_TYPE => STYPE, CPROPS => CPROPS); + end CLNEVENTS_UPDATE; + + /* Удаление события */ + procedure CLNEVENTS_DELETE + ( + NCLNEVENTS in number -- Рег. номер события + ) + is + NCOMPANY PKG_STD.TREF := GET_SESSION_COMPANY(); -- Организация сеанса + begin + /* Удаляем событие */ + P_CLNEVENTS_DELETE(NCOMPANY => NCOMPANY, NRN => NCLNEVENTS); + /* Удаляем значения свойств документа */ + PKG_DOCS_PROPS_VALS.AFTER_DELETE(SUNITCODE => SUNIT_CLNEVENTS, NDOCUMENT => NCLNEVENTS); + end CLNEVENTS_DELETE; + + /* Изменение статуса события */ + procedure CLNEVENTS_STATE_CHANGE + ( + NIDENT in out number, -- Рег. номер отмеченных записей + NSTEP in out number, -- Шаг выполнения (0-прекратить выполнение, 1,2..- номер шага) + NEVENT in out number, -- Событие, для отбора исполнителей + SEVENT_TYPE in out varchar2, -- Тип события, для отбора точек перехода и исполнителей + SEVENT_STAT in out varchar2, -- Cтатус события, текущий - для отбора точек перехода, следующий - для отбора исполнителей + SINIT_PERSON in out varchar2, -- Инициатор–сотрудник, для отбора исполнителей + SINIT_AUTHNAME in out varchar2, -- Инициатор–пользователь, для отбора исполнителей + SCLIENT_CLIENT in out varchar2, -- Клиент–организация, для отбора исполнителей + SCLIENT_PERSON in out varchar2, -- Клиент–сотрудник, для отбора исполнителей + NPOINT in out number, -- Текущая точка маршрута + NPASS in number, -- Точка перехода + NSELECT_EXEC out number, -- Признак необходимости выбора исполнителя + SEXECUTEMETHOD out varchar2, -- Мнемокод метода + SSEND_CLIENT in varchar2, -- Направить – организация + SSEND_DIVISION in varchar2, -- Направить – подразделение + SSEND_POST in varchar2, -- Направить – должность + SSEND_PERFORM in varchar2, -- Направить – должность в подразделении + SSEND_PERSON in varchar2, -- Направить – сотрудник + SSEND_STAFFGRP in varchar2, -- Направить – нештатная структура + SSEND_USER_GROUP in varchar2, -- Направить – группа пользователей + SSEND_USER_NAME in varchar2, -- Направить – пользователь + NSEND_PREDEFINED_EXEC in number, -- Переадресация + NSEND_PREDEFINED_PROC in number, -- Процедура выбора предопределенного исполнителя + SNEXT_STAT in varchar2 := null, -- Следующий статус (при переносе события) + SNOTE_HEADER in varchar2 := null, -- Заголовок примечания (при наличии) + SNOTE in varchar2 := null -- Примечание (при наличии) + ) + is + NCOMPANY PKG_STD.TREF := GET_SESSION_COMPANY(); -- Организация сеанса + NTMP PKG_STD.TREF; -- Заглушка + NSTEP_CURRENT PKG_STD.TREF; -- Номер текущего шага + NNOTE_RN PKG_STD.TREF; -- Рег. номер добавленного примечания + + /* Считывание рег. номер точки маршрута */ + function EVRTPTPASS_NEXT_GET + ( + NCOMPANY in number, -- Рег. номер организации + SEVENT_TYPE in varchar2, -- Мнемокод типа события + NPOINT in number, -- Рег. номер текущей точки + SEVENT_STAT in varchar2, -- Мнемокод текущего статуса события + SNEXT_STAT in varchar2 -- Мнемокод следующего статуса события + ) return number -- Рег. номер точки перехода + is + NNEXT_POINT PKG_STD.TREF; -- Рег. номер следующей точки + NRESULT PKG_STD.TREF; -- Рег. номер точки перехода + begin + /* Определение следующей точки маршрута */ + FIND_EVRPOINTS_BY_STATUS(NFLAG_SMART => 0, + NFLAG_OPTION => 0, + NCOMPANY => NCOMPANY, + SEVENT_TYPE => SEVENT_TYPE, + SEVENT_STATUS => SNEXT_STAT, + NRN => NNEXT_POINT); + /* Считывание рег. номера точки перехода */ + begin + select T.RN + into NRESULT + from EVRTPTPASS T + where T.PRN = NPOINT + and T.NEXT_POINT = NNEXT_POINT; + exception + when others then + P_EXCEPTION(0, + 'Ошибка считывания точки перехода из статуса "%s" в статус "%s".', + SEVENT_STAT, + SNEXT_STAT); + end; + /* Возвращаем результат */ + return NRESULT; + end EVRTPTPASS_NEXT_GET; + begin + /* Определяем номер текущего шага */ + NSTEP_CURRENT := NSTEP; + /* Если это первый шаг */ + if (NSTEP_CURRENT = 1) then + /* Генерируем идентификатор отмеченных записей */ + NIDENT := GEN_IDENT(); + /* Добавляем запись события в отмеченные */ + P_SELECTLIST_BASE_INSERT(NIDENT => NIDENT, + NCOMPANY => NCOMPANY, + NDOCUMENT => NEVENT, + SUNITCODE => SUNIT_CLNEVENTS, + SACTIONCODE => SSELECTLIST_ACTION, + NCRN => null, + NDOCUMENT1 => null, + SUNITCODE1 => null, + SACTIONCODE1 => null, + NRN => NTMP); + end if; + /* Изменяем статус события */ + P_CLNEVENTS_CHANGE_STATE_WEB(NCOMPANY => NCOMPANY, + NIDENT => NIDENT, + NSTEP => NSTEP, + NEVENT => NEVENT, + SEVENT_TYPE => SEVENT_TYPE, + SEVENT_STAT => SEVENT_STAT, + SINIT_PERSON => SINIT_PERSON, + SINIT_AUTHNAME => SINIT_AUTHNAME, + SCLIENT_CLIENT => SCLIENT_CLIENT, + SCLIENT_PERSON => SCLIENT_PERSON, + NPOINT => NPOINT, + NPASS => NPASS, + NSELECT_EXEC => NSELECT_EXEC, + SEXECUTEMETHOD => SEXECUTEMETHOD, + SSEND_CLIENT => SSEND_CLIENT, + SSEND_DIVISION => SSEND_DIVISION, + SSEND_POST => SSEND_POST, + SSEND_PERFORM => SSEND_PERFORM, + SSEND_PERSON => SSEND_PERSON, + SSEND_STAFFGRP => SSEND_STAFFGRP, + SSEND_USER_GROUP => SSEND_USER_GROUP, + SSEND_USER_NAME => SSEND_USER_NAME, + NSEND_PREDEFINED_EXEC => NSEND_PREDEFINED_EXEC, + NSEND_PREDEFINED_PROC => NSEND_PREDEFINED_PROC); + /* Если это первый шаг и уже известен следующий статус */ + if ((NSTEP_CURRENT = 1) and (SNEXT_STAT is not null)) then + /* Сразу выполняем следующее действие */ + P_CLNEVENTS_CHANGE_STATE_WEB(NCOMPANY => NCOMPANY, + NIDENT => NIDENT, + NSTEP => NSTEP, + NEVENT => NEVENT, + SEVENT_TYPE => SEVENT_TYPE, + SEVENT_STAT => SEVENT_STAT, + SINIT_PERSON => SINIT_PERSON, + SINIT_AUTHNAME => SINIT_AUTHNAME, + SCLIENT_CLIENT => SCLIENT_CLIENT, + SCLIENT_PERSON => SCLIENT_PERSON, + NPOINT => NPOINT, + NPASS => EVRTPTPASS_NEXT_GET(NCOMPANY => NCOMPANY, + SEVENT_TYPE => SEVENT_TYPE, + NPOINT => NPOINT, + SEVENT_STAT => SEVENT_STAT, + SNEXT_STAT => SNEXT_STAT), + NSELECT_EXEC => NSELECT_EXEC, + SEXECUTEMETHOD => SEXECUTEMETHOD, + SSEND_CLIENT => SSEND_CLIENT, + SSEND_DIVISION => SSEND_DIVISION, + SSEND_POST => SSEND_POST, + SSEND_PERFORM => SSEND_PERFORM, + SSEND_PERSON => SSEND_PERSON, + SSEND_STAFFGRP => SSEND_STAFFGRP, + SSEND_USER_GROUP => SSEND_USER_GROUP, + SSEND_USER_NAME => SSEND_USER_NAME, + NSEND_PREDEFINED_EXEC => NSEND_PREDEFINED_EXEC, + NSEND_PREDEFINED_PROC => NSEND_PREDEFINED_PROC); + /* Указываем следующий статус */ + SEVENT_STAT := SNEXT_STAT; + end if; + /* Если это последний шаг */ + if (NSTEP_CURRENT = 3) then + /* Если добавлено примечание */ + if (SNOTE is not null) then + P_CLNEVNOTES_INSERT(NCOMPANY => NCOMPANY, + NPRN => NEVENT, + SNOTE_HEADER => SNOTE_HEADER, + SNOTE => SNOTE, + NRN => NNOTE_RN); + end if; + /* Очищаем отмеченные записи */ + P_SELECTLIST_CLEAR(NIDENT => NIDENT); + end if; + exception + when others then + /* Если список отмеченных есть */ + if (NIDENT is not null) then + /* Удаляем в автономной транзакции */ + P_SELECTLIST_CLEAR_AT(NIDENT => NIDENT); + end if; + /* Вернем ошибку */ + PKG_STATE.DIAGNOSTICS_STACKED(); + P_EXCEPTION(0, PKG_STATE.SQL_ERRM()); + end CLNEVENTS_STATE_CHANGE; + + /* Переадресация события */ + procedure CLNEVENTS_SEND + ( + NIDENT in out number, -- Рег. номер отмеченных записей + NSTEP in out number, -- Шаг выполнения + NEVENT in out number, -- Событие, для отбора исполнителей + SEVENT_TYPE in out varchar2, -- Тип события, для отбора точек перехода и исполнителей + SEVENT_STAT in out varchar2, -- Текущий статус события, для отбора исполнителей + SINIT_PERSON in out varchar2, -- Инициатор–сотрудник, для отбора исполнителей + SINIT_AUTHNAME in out varchar2, -- Инициатор–пользователь, для отбора исполнителей + SCLIENT_CLIENT in out varchar2, -- Клиент–организация, для отбора исполнителей + SCLIENT_PERSON in out varchar2, -- Клиент–сотрудник, для отбора исполнителей + SEXECUTEMETHOD out varchar2, -- Мнемокод метода + SSEND_CLIENT in varchar2, -- Направить – организация + SSEND_DIVISION in varchar2, -- Направить – подразделение + SSEND_POST in varchar2, -- Направить – должность + SSEND_PERFORM in varchar2, -- Направить – должность в подразделении + SSEND_PERSON in out varchar2, -- Направить – сотрудник + SSEND_STAFFGRP in varchar2, -- Направить – нештатная структура + SSEND_USER_GROUP in varchar2, -- Направить – группа пользователей + SSEND_USER_NAME in out varchar2, -- Направить – пользователь + NSEND_PREDEFINED_EXEC in number, -- Переадресация + NSEND_PREDEFINED_PROC in number, -- Процедура выбора предопределенного исполнителя + SNOTE_HEADER in varchar2 := null, -- Заголовок примечания (при наличии) + SNOTE in varchar2 := null -- Примечание (при наличии) + ) + is + NCOMPANY PKG_STD.TREF := GET_SESSION_COMPANY(); -- Организация сеанса + NTMP PKG_STD.TREF; -- Заглушка + NSTEP_CURRENT PKG_STD.TREF; -- Номер текущего шага + NNOTE_RN PKG_STD.TREF; -- Рег. номер добавленного примечания + begin + /* Определяем номер текущего шага */ + NSTEP_CURRENT := NSTEP; + /* Если это первый шаг */ + if (NSTEP_CURRENT = 1) then + /* Генерируем идентификатор отмеченных записей */ + NIDENT := GEN_IDENT(); + /* Добавляем запись события в отмеченные */ + P_SELECTLIST_BASE_INSERT(NIDENT => NIDENT, + NCOMPANY => NCOMPANY, + NDOCUMENT => NEVENT, + SUNITCODE => SUNIT_CLNEVENTS, + SACTIONCODE => SSELECTLIST_ACTION, + NCRN => null, + NDOCUMENT1 => null, + SUNITCODE1 => null, + SACTIONCODE1 => null, + NRN => NTMP); + end if; + /* Выполняем переадресацию события */ + P_CLNEVENTS_SEND_WEB(NCOMPANY => NCOMPANY, + NIDENT => NIDENT, + NSTEP => NSTEP, + NEVENT => NEVENT, + SEVENT_TYPE => SEVENT_TYPE, + SEVENT_STAT => SEVENT_STAT, + SINIT_PERSON => SINIT_PERSON, + SINIT_AUTHNAME => SINIT_AUTHNAME, + SCLIENT_CLIENT => SCLIENT_CLIENT, + SCLIENT_PERSON => SCLIENT_PERSON, + SEXECUTEMETHOD => SEXECUTEMETHOD, + SSEND_CLIENT => SSEND_CLIENT, + SSEND_DIVISION => SSEND_DIVISION, + SSEND_POST => SSEND_POST, + SSEND_PERFORM => SSEND_PERFORM, + SSEND_PERSON => SSEND_PERSON, + SSEND_STAFFGRP => SSEND_STAFFGRP, + SSEND_USER_GROUP => SSEND_USER_GROUP, + SSEND_USER_NAME => SSEND_USER_NAME, + NSEND_PREDEFINED_EXEC => NSEND_PREDEFINED_EXEC, + NSEND_PREDEFINED_PROC => NSEND_PREDEFINED_PROC); + /* Если это последний шаг */ + if (NSTEP_CURRENT = 2) then + /* Если добавлено примечание */ + if (SNOTE is not null) then + P_CLNEVNOTES_INSERT(NCOMPANY => NCOMPANY, + NPRN => NEVENT, + SNOTE_HEADER => SNOTE_HEADER, + SNOTE => SNOTE, + NRN => NNOTE_RN); + end if; + /* Очищаем отмеченные записи */ + P_SELECTLIST_CLEAR(NIDENT => NIDENT); + end if; + exception + when others then + /* Если список отмеченных есть */ + if (NIDENT is not null) then + /* Удаляем в автономной транзакции */ + P_SELECTLIST_CLEAR_AT(NIDENT => NIDENT); + end if; + /* Вернем ошибку */ + PKG_STATE.DIAGNOSTICS_STACKED(); + P_EXCEPTION(0, PKG_STATE.SQL_ERRM()); + end CLNEVENTS_SEND; + + /* Возврат в предыдущую точку события */ + procedure CLNEVENTS_RETURN + ( + NCLNEVENTS in number -- Рег. номер события + ) + is + NCOMPANY PKG_STD.TREF := GET_SESSION_COMPANY(); -- Организация сеанса + begin + /* Возвращаем событие */ + P_CLNEVENTS_RETURN_WEB(NCOMPANY => NCOMPANY, NIDENT => null, NEVENT => NCLNEVENTS); + end CLNEVENTS_RETURN; + + /* Считывание маршрутов и исполнителей события */ + procedure CLNEVENTS_GET_INFO_BY_CODE + ( + SEVNTYPE_CODE in varchar2, -- Мнемокод типа события + COUT out clob -- XML с параметрами фильтра по умолчанию + ) + is + NCOMPANY PKG_STD.TREF := GET_SESSION_COMPANY(); -- Рег. номер организации + SSRC PKG_STD.TSTRING; -- Наименование точки маршрута + SDEST PKG_STD.TSTRING; -- Наименование точки перехода + begin + /* Формируем XML */ + PKG_XFAST.PROLOGUE(ITYPE => PKG_XFAST.CONTENT_); + PKG_XFAST.DOWN_NODE(SNAME => 'DATA'); + /* Цикл по точкам маршрута и точкам перехода */ + for REC in (select CST1.EVNSTAT_CODE SSRC, + CST2.EVNSTAT_CODE SDEST + from EVRTPTPASS EPS, + EVRTPOINTS EP1, + CLNEVNTYPSTS CT1, + CLNEVNSTATS CST1, + EVRTPOINTS EP2, + CLNEVNTYPSTS CT2, + CLNEVNSTATS CST2 + where EPS.PRN in (select EP.RN + from EVROUTES ER, + CLNEVNTYPES C, + EVRTPOINTS EP + where ER.COMPANY = NCOMPANY + and ER.EVENT_TYPE = C.RN + and C.EVNTYPE_CODE = SEVNTYPE_CODE + and EP.PRN = ER.RN) + and EPS.FORCE_ONLY = 0 + and EPS.PRN = EP1.RN + and EP1.EVENT_STATUS = CT1.RN + and CT1.EVENT_STATUS = CST1.RN + and EPS.NEXT_POINT = EP2.RN + and EP2.EVENT_STATUS = CT2.RN + and CT2.EVENT_STATUS = CST2.RN) + loop + /* Добавляем информацию о маршруте */ + PKG_XFAST.DOWN_NODE(SNAME => 'XEVROUTES'); + /* Начало перехода */ + PKG_XFAST.DOWN_NODE(SNAME => 'SSOURCE'); + PKG_XFAST.VALUE(SVALUE => REC.SSRC); + PKG_XFAST.UP(); + /* Конец перехода */ + PKG_XFAST.DOWN_NODE(SNAME => 'SDESTINATION'); + PKG_XFAST.VALUE(SVALUE => REC.SDEST); + PKG_XFAST.UP(); + PKG_XFAST.UP(); + end loop; + /* Цикл по точкам маршрута */ + for REC in (select CST.EVNSTAT_CODE, + CST.EVNSTAT_NAME, + EP.COMMENTARY, + EP.ADDNOTE_ONCHST, + EP.ADDNOTE_ONSEND, + EP.BAN_UPDATE + from EVROUTES ER, + CLNEVNTYPES C, + EVRTPOINTS EP, + CLNEVNTYPSTS CT, + CLNEVNSTATS CST + where ER.COMPANY = NCOMPANY + and ER.EVENT_TYPE = C.RN + and C.EVNTYPE_CODE = SEVNTYPE_CODE + and EP.PRN = ER.RN + and EP.EVENT_STATUS = CT.RN + and CT.EVENT_STATUS = CST.RN) + loop + /* Добавляем информацию о точке маршрута */ + PKG_XFAST.DOWN_NODE(SNAME => 'XEVPOINTS'); + /* Мнемокод точки маршрута */ + PKG_XFAST.DOWN_NODE(SNAME => 'SEVPOINT'); + PKG_XFAST.VALUE(SVALUE => REC.EVNSTAT_CODE); + PKG_XFAST.UP(); + /* Описание в точке маршрута */ + PKG_XFAST.DOWN_NODE(SNAME => 'SEVPOINT_DESCR'); + PKG_XFAST.VALUE(SVALUE => COALESCE(REC.COMMENTARY, REC.EVNSTAT_NAME)); + PKG_XFAST.UP(); + /* Признак добавления примечания при переходе в точку маршрута */ + PKG_XFAST.DOWN_NODE(SNAME => 'ADDNOTE_ONCHST'); + PKG_XFAST.VALUE(BVALUE => INT2BOOL(REC.ADDNOTE_ONCHST)); + PKG_XFAST.UP(); + /* Признак добавления примечания при перенаправлении в точке маршрута */ + PKG_XFAST.DOWN_NODE(SNAME => 'ADDNOTE_ONSEND'); + PKG_XFAST.VALUE(BVALUE => INT2BOOL(REC.ADDNOTE_ONSEND)); + PKG_XFAST.UP(); + /* Признак запрета исправления события в точке маршрута */ + PKG_XFAST.DOWN_NODE(SNAME => 'BAN_UPDATE'); + PKG_XFAST.VALUE(BVALUE => INT2BOOL(REC.BAN_UPDATE)); + PKG_XFAST.UP(); + PKG_XFAST.UP(); + end loop; + /* Цикл по типам заголовков примечаний */ + for REC in (select NT.NAME + from CLNEVNTYPES ET, + CLNEVNTYPENOTES TN, + CLNEVNTNOTETYPES NT + where ET.COMPANY = NCOMPANY + and ET.EVNTYPE_CODE = SEVNTYPE_CODE + and TN.PRN = ET.RN + and NT.RN = TN.NOTE_TYPE) + loop + /* Добавляем информацию о заголовке примечания */ + PKG_XFAST.DOWN_NODE(SNAME => 'XNOTETYPES'); + /* Наименование типа заголовка примечания */ + PKG_XFAST.DOWN_NODE(SNAME => 'SNAME'); + PKG_XFAST.VALUE(SVALUE => REC.NAME); + PKG_XFAST.UP(); + PKG_XFAST.UP(); + end loop; + PKG_XFAST.UP(); + /* Сериализуем в CLOB */ + COUT := PKG_XFAST.SERIALIZE_TO_CLOB(); + PKG_XFAST.EPILOGUE(); + end CLNEVENTS_GET_INFO_BY_CODE; + + /* Считывание учётных документов события */ + procedure CLNEVENTS_DOCLINKS_GET + ( + SEVNTYPE_CODE in varchar2, -- Мнемокод типа события + COUT out clob -- XML с параметрами фильтра по умолчанию + ) + is + NCOMPANY PKG_STD.TREF := GET_SESSION_COMPANY(); -- Рег. номер организации + begin + /* Формируем XML */ + PKG_XFAST.PROLOGUE(ITYPE => PKG_XFAST.CONTENT_); + PKG_XFAST.DOWN_NODE(SNAME => 'DATA'); + /* Цикл по учётным документам */ + for REC in (select C.LINKED_RN NRN, + C.LINKED_UNIT SUNIT + from CLNEVENTS C, + CLNEVNTYPES CT + where C.COMPANY = NCOMPANY + and C.EVENT_TYPE = CT.RN + and CT.EVNTYPE_CODE = SEVNTYPE_CODE + and C.LINKED_RN is not null) + loop + PKG_XFAST.DOWN_NODE(SNAME => 'XDOCLINKS'); + /* Идентификатор */ + PKG_XFAST.DOWN_NODE(SNAME => 'NRN'); + PKG_XFAST.VALUE(NVALUE => REC.NRN); + PKG_XFAST.UP(); + /* Описатель документа */ + PKG_XFAST.DOWN_NODE(SNAME => 'SDESCR'); + PKG_XFAST.VALUE(SVALUE => UTL_UNITLIST_DESCR_GET(SUNIT_CODE => REC.SUNIT, NDOCUMENT => REC.NRN)); + PKG_XFAST.UP(); + PKG_XFAST.UP(); + end loop; + PKG_XFAST.UP(); + /* Сериализуем в CLOB */ + COUT := PKG_XFAST.SERIALIZE_TO_CLOB(); + PKG_XFAST.EPILOGUE(); + end CLNEVENTS_DOCLINKS_GET; + + /* Считывание списка рег. номеров дочерних каталогов (включая основной) */ + procedure CLNEVENTS_SUBCATALOGS_GET + ( + SCRN_NAME in varchar2, -- Наименование каталога + NSUBCAT in number := 0, -- Признак включения подкаталогов (0 - нет, 1 - да) + SRESULT out varchar2 -- Список рег. номеров каталогов + ) + is + NCOMPANY PKG_STD.TREF := GET_SESSION_COMPANY(); -- Рег. номер организации + NCRN_MAIN PKG_STD.TREF; -- Рег. номер основного каталога + begin + /* Считываем рег. номер основого каталога */ + FIND_ACATALOG_NAME(NFLAG_SMART => 0, + NCOMPANY => NCOMPANY, + NVERSION => null, + SUNITCODE => SUNIT_CLNEVENTS, + SNAME => SCRN_NAME, + NRN => NCRN_MAIN); + /* Если требуется включить подкаталоги */ + if (NSUBCAT = 1) then + /* Цикл по иерерахии каталогов */ + for REC in (select A.RN from ACATALOG A start with A.RN = NCRN_MAIN connect by prior A.RN = A.CRN) + loop + SRESULT := SRESULT || REC.RN || ';'; + end loop; + else + /* Указываем только основной каталог */ + SRESULT := TO_CHAR(NCRN_MAIN); + end if; + /* Удаляем лишний ";" */ + SRESULT := RTRIM(SRESULT, ';'); + end CLNEVENTS_SUBCATALOGS_GET; + + /* Формирование условий отбора фильтра */ + procedure CLNEVENTS_COND + is + begin + /* Установка главной таблицы */ + PKG_COND_BROKER.SET_TABLE(STABLE_NAME => 'CLNEVENTS'); + /* Формирование условий отбора */ + /* Условие по состоянию события */ + PKG_COND_BROKER.ADD_CONDITION_BETWEEN(SCOLUMN_NAME => 'CLOSED', + SCONDITION_NAME_FROM => 'BYEV_CLOSED_From', + SCONDITION_NAME_TO => 'BYEV_CLOSED_To'); + /* Условие по типу события */ + PKG_COND_BROKER.ADD_CONDITION_CODE(SCOLUMN_NAME => 'EVNTYPE_CODE', + SCONDITION_NAME => 'BYEV_TYPE', + SJOINS => 'EVENT_TYPE <- RN;CLNEVNTYPES'); + /* Условие по каталогу события */ + PKG_COND_BROKER.ADD_CONDITION_ENUM(SCOLUMN_NAME => 'CRN', SCONDITION_NAME => 'BYEV_CRN'); + /* Условие по исполнителю */ + PKG_COND_BROKER.ADD_CONDITION_CODE(SCOLUMN_NAME => 'AGNABBR', + SCONDITION_NAME => 'BYEV_S_PERSON', + SJOINS => 'SEND_PERSON <- RN;CLNPERSONS;PERS_AGENT <- RN;AGNLIST'); + /* Условие по подразделению */ + PKG_COND_BROKER.ADD_CONDITION_CODE(SCOLUMN_NAME => 'CODE', + SCONDITION_NAME => 'BYEV_S_DIVISION', + SJOINS => 'SEND_DIVISION <- RN;INS_DEPARTMENT'); + /* Условие по группе пользователей */ + PKG_COND_BROKER.ADD_CONDITION_CODE(SCOLUMN_NAME => 'CODE', + SCONDITION_NAME => 'BYEV_S_USER_GROUP', + SJOINS => 'SEND_USER_GROUP <- RN;USERGRP'); + /* Условие по учётному документу */ + PKG_COND_BROKER.ADD_CONDITION_COMPARE(SCOLUMN_NAME => 'LINKED_RN', + SOPERATION => '=', + SCONDITION_NAME => 'BYEV_LINKED_RN'); + end CLNEVENTS_COND; + + /* Считывание настройки раздела "События" для пользователя */ + procedure CLNEVENTS_DP_RULES_GET + ( + COUT out clob -- XML с настройкой раздела + ) + is + SAUTHID PKG_STD.TSTRING := PKG_SESSION.GET_UTILIZER(); -- Пользователь + NCOMPANY PKG_STD.TREF := GET_SESSION_COMPANY(); -- Рег. номер организации + BXML blob; -- XML профиля пользователя для раздела "События" + RDOC PKG_XPATH.TDOCUMENT; -- Документ XML + RNODE_ROOT PKG_XPATH.TNODE; -- Корневой узел + RNODE_MAIN PKG_XPATH.TNODE; -- Основной узел + RNODE_GRIDFRAME PKG_XPATH.TNODE; -- Узел "gridFrame" + RNODE_DECOR_RULES PKG_XPATH.TNODE; -- Узел "decorationRules" + RNODE_RULE PKG_XPATH.TNODE; -- Узел "rule" + RNODE_RULE_LIST PKG_XPATH.TNODES; -- Список узлов "rule" + SFIELD PKG_STD.TSTRING; -- Код свойства документа (в формате "DP_<РЕГ_НОМЕР>") + SCOLOR PKG_STD.TSTRING; -- Код цвета + SDATA_TYPE PKG_STD.TSTRING; -- Тип данных + SDP_NAME PKG_STD.TSTRING; -- Наименование свойства документа + NFROM_VALUE PKG_STD.TNUMBER; -- Значение с (number) + NTO_VALUE PKG_STD.TNUMBER; -- Значение по (number) + SFROM_VALUE PKG_STD.TSTRING; -- Значение с (string) + STO_VALUE PKG_STD.TSTRING; -- Значение по (string) + DFROM_VALUE PKG_STD.TLDATE; -- Значение с (date) + DTO_VALUE PKG_STD.TLDATE; -- Значение по (date) + + /* Считывание наименования свойства документа */ + function DOCS_PROPS_NAME_GET + ( + SFIELD in varchar2 -- Код свойства документа (в формате "DP_<РЕГ_НОМЕР>") + ) return varchar2 -- Наименование свойства документа + is + NRN PKG_STD.TREF; -- Рег. номер свойства документа + SRESULT PKG_STD.TSTRING; -- Наименование доп. свойства + begin + /* Определяем рег. номер свойства документа по формату "DP_<РЕГ_НОМЕР>" */ + NRN := TO_NUMBER(SUBSTR(SFIELD, INSTR(SFIELD, '_') + 1)); + /* Считываем наименование из записи свойства документа */ + begin + select T.NAME into SRESULT from DOCS_PROPS T where T.RN = NRN; + exception + when NO_DATA_FOUND then + P_EXCEPTION(0, + 'Ошибка считывания записи свойства документа с рег. номером (%s).'); + when others then + P_EXCEPTION(0, + 'Ошибка считывания наименования свойства документа с рег. номером (%s).'); + end; + /* Возвращаем результат */ + return SRESULT; + end DOCS_PROPS_NAME_GET; + + /* Считывание профиля пользователя для раздела "События" */ + function USERPROFILES_GET + ( + NCOMPANY in number, -- Рег. номер организации + SAUTHID in varchar2 -- Мнемокод пользователя + ) return blob -- Профиль пользователя для раздела "События" + is + BRESULT blob; -- Профиль пользователя для раздела "События" + begin + /* Считываем профиль пользователя для раздела "События" */ + begin + select U.USERDATA + into BRESULT + from USERPROFILES U + where U.COMPANY = NCOMPANY + and U.AUTHID = SAUTHID + and U.REC_TYPE = 1 + and U.UNITMODE = 0 + and U.UNITCODE = SUNIT_CLNEVENTS; + exception + when others then + P_EXCEPTION(0, + 'Профиль пользователя "%s" для раздела "События" не найден.', + SAUTHID); + end; + /* Возвращаем результат */ + return BRESULT; + end USERPROFILES_GET; + begin + /* Считываем профиль пользователя для раздела "События" */ + BXML := USERPROFILES_GET(NCOMPANY => NCOMPANY, SAUTHID => SAUTHID); + begin + /* Формируем XML данных */ + PKG_XFAST.PROLOGUE(ITYPE => PKG_XFAST.CONTENT_); + PKG_XFAST.DOWN_NODE(SNAME => 'DATA'); + /* Загрузка XML */ + RDOC := PKG_XPATH.PARSE_FROM_BLOB(LBXML => BXML); + RNODE_ROOT := PKG_XPATH.ROOT_NODE(RDOCUMENT => RDOC); + /* Обработка XML */ + RNODE_MAIN := PKG_XPATH.FIRST_NODE(RPARENT_NODE => RNODE_ROOT); + RNODE_GRIDFRAME := PKG_XPATH.SINGLE_NODE(RPARENT_NODE => RNODE_MAIN, SPATTERN => 'gridFrame'); + RNODE_DECOR_RULES := PKG_XPATH.SINGLE_NODE(RPARENT_NODE => RNODE_GRIDFRAME, SPATTERN => 'decorationRules'); + RNODE_RULE_LIST := PKG_XPATH.LIST_NODES(RPARENT_NODE => RNODE_DECOR_RULES, SPATTERN => 'rule'); + /* Цикл по узлам внутри узла rule */ + for I in 1 .. PKG_XPATH.COUNT_NODES(RNODES => RNODE_RULE_LIST) + loop + /* Считываем текущее правило */ + RNODE_RULE := PKG_XPATH.ITEM_NODE(RNODES => RNODE_RULE_LIST, INUMBER => I); + /* Атрибуты */ + SFIELD := PKG_XPATH.ATTRIBUTE(RNODE => RNODE_RULE, SNAME => 'field'); + SCOLOR := PKG_XPATH.ATTRIBUTE(RNODE => RNODE_RULE, SNAME => 'color'); + SDATA_TYPE := PKG_XPATH.ATTRIBUTE(RNODE => RNODE_RULE, SNAME => 'dataType'); + PKG_XFAST.DOWN_NODE(SNAME => 'XRULES'); + /* Код правила */ + PKG_XFAST.DOWN_NODE(SNAME => 'SFIELD'); + PKG_XFAST.VALUE(SVALUE => SFIELD); + PKG_XFAST.UP(); + /* Считываем наименование свойства документа */ + SDP_NAME := DOCS_PROPS_NAME_GET(SFIELD => SFIELD); + /* Наименование доп. свойства */ + PKG_XFAST.DOWN_NODE(SNAME => 'SDP_NAME'); + PKG_XFAST.VALUE(SVALUE => SDP_NAME); + PKG_XFAST.UP(); + /* Код цвета */ + PKG_XFAST.DOWN_NODE(SNAME => 'SCOLOR'); + PKG_XFAST.VALUE(SVALUE => SCOLOR); + PKG_XFAST.UP(); + /* Тип значения */ + PKG_XFAST.DOWN_NODE(SNAME => 'STYPE'); + PKG_XFAST.VALUE(SVALUE => SDATA_TYPE); + PKG_XFAST.UP(); + /* Исходим от типа данных */ + case SDATA_TYPE + /* Число */ + when 'number' then + NFROM_VALUE := PKG_XPATH.VALUE_NUM(RNODE => PKG_XPATH.SINGLE_NODE(RPARENT_NODE => RNODE_RULE, + SPATTERN => 'fromValue')); + NTO_VALUE := PKG_XPATH.VALUE_NUM(RNODE => PKG_XPATH.SINGLE_NODE(RPARENT_NODE => RNODE_RULE, + SPATTERN => 'toValue')); + /* Число с */ + PKG_XFAST.DOWN_NODE(SNAME => 'NFROM'); + PKG_XFAST.VALUE(NVALUE => NFROM_VALUE); + PKG_XFAST.UP(); + /* Число по */ + PKG_XFAST.DOWN_NODE(SNAME => 'NTO'); + PKG_XFAST.VALUE(NVALUE => NTO_VALUE); + PKG_XFAST.UP(); + /* Строка */ + when 'string' then + SFROM_VALUE := PKG_XPATH.VALUE(RNODE => PKG_XPATH.SINGLE_NODE(RPARENT_NODE => RNODE_RULE, + SPATTERN => 'fromValue')); + STO_VALUE := PKG_XPATH.VALUE(RNODE => PKG_XPATH.SINGLE_NODE(RPARENT_NODE => RNODE_RULE, + SPATTERN => 'toValue')); + /* Строка с */ + PKG_XFAST.DOWN_NODE(SNAME => 'SFROM'); + PKG_XFAST.VALUE(SVALUE => SFROM_VALUE); + PKG_XFAST.UP(); + /* Строка по */ + PKG_XFAST.DOWN_NODE(SNAME => 'STO'); + PKG_XFAST.VALUE(SVALUE => STO_VALUE); + PKG_XFAST.UP(); + /* Дата */ + else + DFROM_VALUE := PKG_XPATH.VALUE_DATE(RNODE => PKG_XPATH.SINGLE_NODE(RPARENT_NODE => RNODE_RULE, + SPATTERN => 'fromValue')); + DTO_VALUE := PKG_XPATH.VALUE_DATE(RNODE => PKG_XPATH.SINGLE_NODE(RPARENT_NODE => RNODE_RULE, + SPATTERN => 'toValue')); + /* Дата с */ + PKG_XFAST.DOWN_NODE(SNAME => 'DFROM'); + PKG_XFAST.VALUE(DVALUE => DFROM_VALUE); + PKG_XFAST.UP(); + /* Дата по */ + PKG_XFAST.DOWN_NODE(SNAME => 'DTO'); + PKG_XFAST.VALUE(DVALUE => DTO_VALUE); + PKG_XFAST.UP(); + end case; + PKG_XFAST.UP(); + end loop; + PKG_XPATH.FREE(RDOCUMENT => RDOC); + PKG_XFAST.UP(); + /* Сериализуем в CLOB */ + COUT := PKG_XFAST.SERIALIZE_TO_CLOB(); + PKG_XFAST.EPILOGUE(); + exception + when others then + PKG_XPATH.FREE(RDOCUMENT => RDOC); + PKG_XFAST.EPILOGUE(); + end; + end CLNEVENTS_DP_RULES_GET; + + /* Считывание свойств раздела "События" */ + procedure CLNEVENTS_PROPS_GET + ( + SEVNTYPE_CODE in varchar2, -- Мнемокод типа события + COUT out clob -- XML со свойствами раздела + ) + is + NCOMPANY PKG_STD.TREF := GET_SESSION_COMPANY(); -- Рег. номер организации + begin + /* Формируем XML данных */ + PKG_XFAST.PROLOGUE(ITYPE => PKG_XFAST.CONTENT_); + PKG_XFAST.DOWN_NODE(SNAME => 'DATA'); + /* Цикл по свойствам документа */ + for REC in (select CEP.PROPERTY, + CEP.PROPERTY_LINK, + DPL.NAME, + DPL.READONLY, + DPL.CHECK_VALUE, + DPL.CHECK_UNIQUE, + DPL.REQUIRE, + DPL.DUPLICATE_VALUE, + DPL.ACCESS_MODE, + DPL.SHOW_IN_GRID, + DPL.DEFAULT_STR, + DPL.DEFAULT_NUM, + DPL.DEFAULT_DATE, + DP.ENTRY_TYPE, + DP.FORMAT, + DP.DATA_SUBTYPE, + DP.NUM_WIDTH, + DP.NUM_PRECISION, + DP.STR_WIDTH, + DP.UNITCODE, + DP.PARAM_RN, + UP.IN_CODE, + UP.OUT_CODE, + DP.SHOW_METHOD_RN, + UM.METHOD_CODE, + DP.EXTRA_DICT_RN, + DP.INIT_RN + from CLEVTPROPS CEP, + CLNEVNTYPES CET, + DOCS_PROPS DP, + DOCS_PROPS_LINKS DPL, + UNITPARAMS UP, + UNIT_SHOWMETHODS UM + where CEP.PRN = CET.RN + and CET.EVNTYPE_CODE = SEVNTYPE_CODE + and CEP.COMPANY = NCOMPANY + and CEP.PROPERTY = DP.RN(+) + and CEP.PROPERTY_LINK = DPL.RN(+) + and (DP.PARAM_RN = UP.RN(+) and DP.UNITCODE = UP.UNITCODE(+)) + and (DP.SHOW_METHOD_RN = UM.RN(+) and DP.UNITCODE = UM.UNITCODE(+)) + order by DPL.POSITION) + loop + PKG_XFAST.DOWN_NODE(SNAME => 'XPROPS'); + /* Рег. номер свойства */ + PKG_XFAST.DOWN_NODE(SNAME => 'NRN'); + PKG_XFAST.VALUE(NVALUE => REC.PROPERTY); + PKG_XFAST.UP(); + /* Наименование свойства */ + PKG_XFAST.DOWN_NODE(SNAME => 'SNAME'); + PKG_XFAST.VALUE(SVALUE => REC.NAME); + PKG_XFAST.UP(); + /* Признак "только для чтения" */ + PKG_XFAST.DOWN_NODE(SNAME => 'BREADONLY'); + PKG_XFAST.VALUE(BVALUE => INT2BOOL(REC.READONLY)); + PKG_XFAST.UP(); + /* Признак "проверка/синхронизация вводимого значения" */ + PKG_XFAST.DOWN_NODE(SNAME => 'CHECK_VALUE'); + PKG_XFAST.VALUE(BVALUE => INT2BOOL(REC.CHECK_VALUE)); + PKG_XFAST.UP(); + /* Признак "проверка вводимого значения на уникальность" */ + PKG_XFAST.DOWN_NODE(SNAME => 'CHECK_UNIQUE'); + PKG_XFAST.VALUE(BVALUE => INT2BOOL(REC.CHECK_UNIQUE)); + PKG_XFAST.UP(); + /* Признак "обязательность к заполнению" */ + PKG_XFAST.DOWN_NODE(SNAME => 'BREQUIRE'); + PKG_XFAST.VALUE(BVALUE => INT2BOOL(REC.REQUIRE)); + PKG_XFAST.UP(); + /* Признак размножения */ + PKG_XFAST.DOWN_NODE(SNAME => 'DUPLICATE_VALUE'); + PKG_XFAST.VALUE(BVALUE => INT2BOOL(REC.DUPLICATE_VALUE)); + PKG_XFAST.UP(); + /* Уровень доступа к значениям свойства */ + PKG_XFAST.DOWN_NODE(SNAME => 'NACCESS_MODE'); + PKG_XFAST.VALUE(NVALUE => REC.ACCESS_MODE); + PKG_XFAST.UP(); + /* Признак "показывать значение свойства в гриде раздела" */ + PKG_XFAST.DOWN_NODE(SNAME => 'BSHOW_IN_GRID'); + PKG_XFAST.VALUE(BVALUE => INT2BOOL(REC.SHOW_IN_GRID)); + PKG_XFAST.UP(); + /* Строка "по умолчанию" */ + PKG_XFAST.DOWN_NODE(SNAME => 'SDEFAULT_STR'); + PKG_XFAST.VALUE(SVALUE => REC.DEFAULT_STR); + PKG_XFAST.UP(); + /* Число "по умолчанию" */ + PKG_XFAST.DOWN_NODE(SNAME => 'NDEFAULT_NUM'); + PKG_XFAST.VALUE(SVALUE => REC.DEFAULT_NUM); + PKG_XFAST.UP(); + /* Дата "по умолчанию" */ + PKG_XFAST.DOWN_NODE(SNAME => 'DDEFAULT_DATE'); + PKG_XFAST.VALUE(DVALUE => REC.DEFAULT_DATE); + PKG_XFAST.UP(); + /* Способ формирования */ + PKG_XFAST.DOWN_NODE(SNAME => 'NENTRY_TYPE'); + PKG_XFAST.VALUE(NVALUE => REC.ENTRY_TYPE); + PKG_XFAST.UP(); + /* Формат данных */ + PKG_XFAST.DOWN_NODE(SNAME => 'NFORMAT'); + PKG_XFAST.VALUE(NVALUE => REC.FORMAT); + PKG_XFAST.UP(); + /* Подтип */ + PKG_XFAST.DOWN_NODE(SNAME => 'NDATA_SUBTYPE'); + PKG_XFAST.VALUE(NVALUE => REC.DATA_SUBTYPE); + PKG_XFAST.UP(); + /* Длина числа */ + PKG_XFAST.DOWN_NODE(SNAME => 'NNUM_WIDTH'); + PKG_XFAST.VALUE(NVALUE => REC.NUM_WIDTH); + PKG_XFAST.UP(); + /* Точность числа */ + PKG_XFAST.DOWN_NODE(SNAME => 'NNUM_PRECISION'); + PKG_XFAST.VALUE(NVALUE => REC.NUM_PRECISION); + PKG_XFAST.UP(); + /* Длина строки */ + PKG_XFAST.DOWN_NODE(SNAME => 'NSTR_WIDTH'); + PKG_XFAST.VALUE(NVALUE => REC.STR_WIDTH); + PKG_XFAST.UP(); + /* Код раздела */ + PKG_XFAST.DOWN_NODE(SNAME => 'SUNITCODE'); + PKG_XFAST.VALUE(SVALUE => REC.UNITCODE); + PKG_XFAST.UP(); + /* Рег. номер параметра раздела */ + PKG_XFAST.DOWN_NODE(SNAME => 'NPARAM_RN'); + PKG_XFAST.VALUE(NVALUE => REC.PARAM_RN); + PKG_XFAST.UP(); + /* Код параметра раздела (входящий) */ + PKG_XFAST.DOWN_NODE(SNAME => 'SPARAM_IN_CODE'); + PKG_XFAST.VALUE(SVALUE => REC.IN_CODE); + PKG_XFAST.UP(); + /* Код параметра раздела (исходящий) */ + PKG_XFAST.DOWN_NODE(SNAME => 'SPARAM_OUT_CODE'); + PKG_XFAST.VALUE(SVALUE => REC.OUT_CODE); + PKG_XFAST.UP(); + /* Рег. номер метода вызова */ + PKG_XFAST.DOWN_NODE(SNAME => 'NSHOW_METHOD_RN'); + PKG_XFAST.VALUE(NVALUE => REC.SHOW_METHOD_RN); + PKG_XFAST.UP(); + /* Код метода вызова */ + PKG_XFAST.DOWN_NODE(SNAME => 'SMETHOD_CODE'); + PKG_XFAST.VALUE(SVALUE => REC.METHOD_CODE); + PKG_XFAST.UP(); + /* Рег. номер дополнительного словаря */ + PKG_XFAST.DOWN_NODE(SNAME => 'NEXTRA_DICT_RN'); + PKG_XFAST.VALUE(NVALUE => REC.EXTRA_DICT_RN); + PKG_XFAST.UP(); + /* Рег. номер свойства инициализации */ + PKG_XFAST.DOWN_NODE(SNAME => 'NINIT_RN'); + PKG_XFAST.VALUE(NVALUE => REC.INIT_RN); + PKG_XFAST.UP(); + /* Отформатированный рег. номер (как в доп. свойствах задачи) */ + PKG_XFAST.DOWN_NODE(SNAME => 'SFORMATTED_ID'); + PKG_XFAST.VALUE(SVALUE => UTL_DP_FORMATTED_ID_GET(NTYPE => REC.FORMAT, NRN => REC.PROPERTY)); + PKG_XFAST.UP(); + PKG_XFAST.UP(); + end loop; + PKG_XFAST.UP(); + /* Сериализуем в CLOB */ + COUT := PKG_XFAST.SERIALIZE_TO_CLOB(); + PKG_XFAST.EPILOGUE(); + end CLNEVENTS_PROPS_GET; + + /* Загрузка статусов типа события */ + procedure CLNEVNSTATS_LOAD + ( + SCLNEVNTYPES in varchar2, -- Мнемокод типа события + COUT out clob -- Статусы типа события + ) + is + NCOMPANY PKG_STD.TREF := GET_SESSION_COMPANY(); -- Организация сеанса + NINDEX PKG_STD.TREF := 0; -- Индекс статуса + begin + /* Начинаем формирование XML */ + PKG_XFAST.PROLOGUE(ITYPE => PKG_XFAST.CONTENT_); + /* Открываем статусы */ + PKG_XFAST.DOWN_NODE(SNAME => 'XCLNEVNSTATS'); + /* Считываем статусы типа события */ + for REC in (select S.RN, + S.EVNSTAT_CODE, + S.EVNSTAT_NAME, + (select EP.COMMENTARY + from EVROUTES ER, + EVRTPOINTS EP + where ER.EVENT_TYPE = T.RN + and EP.PRN = ER.RN + and EP.EVENT_STATUS = TS.RN + and EP.COMMENTARY is not null + and rownum = 1) COMMENTARY + from CLNEVNTYPES T, + CLNEVNTYPSTS TS, + CLNEVNSTATS S + where T.COMPANY = NCOMPANY + and T.EVNTYPE_CODE = SCLNEVNTYPES + and TS.PRN = T.RN + and TS.EVENT_STATUS = S.RN + order by TS.DEFAULT_STATUS desc, + S.EVNSTAT_CODE) + loop + /* Открываем статус */ + PKG_XFAST.DOWN_NODE(SNAME => 'XSTATUS'); + /* Индекс статуса */ + PKG_XFAST.DOWN_NODE(SNAME => 'ID'); + PKG_XFAST.VALUE(NVALUE => NINDEX); + PKG_XFAST.UP(); + /* Рег. номер статуса */ + PKG_XFAST.DOWN_NODE(SNAME => 'NRN'); + PKG_XFAST.VALUE(NVALUE => REC.RN); + PKG_XFAST.UP(); + /* Мнемокод статуса */ + PKG_XFAST.DOWN_NODE(SNAME => 'SEVNSTAT_CODE'); + PKG_XFAST.VALUE(SVALUE => REC.EVNSTAT_CODE); + PKG_XFAST.UP(); + /* Наименование статуса */ + PKG_XFAST.DOWN_NODE(SNAME => 'SEVNSTAT_NAME'); + PKG_XFAST.VALUE(SVALUE => REC.EVNSTAT_NAME); + PKG_XFAST.UP(); + /* Описание в точке маршрута */ + PKG_XFAST.DOWN_NODE(SNAME => 'SEVPOINT_DESCR'); + PKG_XFAST.VALUE(SVALUE => COALESCE(REC.COMMENTARY, REC.EVNSTAT_NAME)); + PKG_XFAST.UP(); + /* Закрываем статус */ + PKG_XFAST.UP(); + /* Увеличиваем индекс статуса */ + NINDEX := NINDEX + 1; + end loop; + /* Закрываем статусы */ + PKG_XFAST.UP(); + /* Сериализуем */ + COUT := PKG_XFAST.SERIALIZE_TO_CLOB(); + /* Завершаем формирование XML */ + PKG_XFAST.EPILOGUE(); + exception + when others then + /* Завершаем формирование XML */ + PKG_XFAST.EPILOGUE(); + /* Вернем ошибку */ + PKG_STATE.DIAGNOSTICS_STACKED(); + P_EXCEPTION(0, PKG_STATE.SQL_ERRM()); + end CLNEVNSTATS_LOAD; + +end PKG_P8PANELS_CLNTTSKBRD; +/