merge upstream

This commit is contained in:
Dollerok 2026-02-06 14:25:25 +03:00
commit b24c3c5689
4 changed files with 201 additions and 108 deletions

View File

@ -7,7 +7,7 @@
//Подключение библиотек
//---------------------
import React, { useState, useEffect, useContext } from "react"; //Классы React
import React, { useState, useEffect, useContext, useCallback } from "react"; //Классы React
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
import { object2Base64XML, formatDateRF } from "../../core/utils"; //Вспомогательные функции
@ -32,17 +32,60 @@ export const useFilteredPlans = (plans, filter) => {
return filteredPlans;
};
//Хук для основной таблицы
const useDeptCostProdPlans = () => {
//Хук для планов
const useDeptCostProdPlans = month => {
//Собственное состояние - таблица данных
const [state, setState] = useState({
init: false,
loaded: false,
showPlanList: false,
rows: [],
reload: true,
selected: {},
currentMonth: ""
});
//Подключение к контексту взаимодействия с сервером
const { executeStored } = useContext(BackEndСtx);
//При подключении компонента к странице
useEffect(() => {
const initPlans = async () => {
const data = await executeStored({
stored: "PKG_P8PANELS_MECHREC.FCPRODPLAN_DEPT_INIT",
args: { SMONTH: month },
respArg: "COUT",
isArray: name => name === "XFCPRODPLANS",
attributeValueProcessor: (name, val) => (name === "SPERIOD" ? undefined : val)
});
setState(pv => ({
...pv,
init: true,
rows: [...(data?.XFCPRODPLANS || [])],
loaded: true,
reload: false,
selected: {},
currentMonth: month
}));
};
//Если месяц указан и он не соответствует текущим данным
if (month && month !== state.currentMonth) {
initPlans();
}
}, [executeStored, month, state.currentMonth]);
//Возвращаем данные
return [state, setState];
};
//Хук для информации о плане
const useDeptCostProdPlanInfo = plan => {
//Собственное состояние - таблица данных
const [state, setState] = useState({
init: false,
showPlanList: false,
showIncomeFromDeps: null,
showFcroutelst: null,
planList: [],
planListLoaded: false,
selectedPlan: {},
dataLoaded: false,
columnsDef: [],
orders: null,
@ -57,14 +100,43 @@ const useDeptCostProdPlans = () => {
//Подключение к контексту взаимодействия с сервером
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
//При необходимости очистки данных о плане
const handleClear = useCallback(
() =>
setState(pv => ({
...pv,
init: false,
showPlanList: false,
showIncomeFromDeps: null,
showFcroutelst: null,
dataLoaded: false,
columnsDef: [],
orders: null,
rows: [],
reload: true,
pageNumber: 1,
morePages: true,
fixedHeader: false,
fixedColumns: 0
})),
[]
);
//При изменении состояния сортировки
const handleOrderChanged = ({ orders }) => setState(pv => ({ ...pv, orders: [...orders], pageNumber: 1, reload: true }));
//При изменении количества отображаемых страниц
const handlePagesCountChanged = () => setState(pv => ({ ...pv, pageNumber: pv.pageNumber + 1, reload: true }));
//При необходимости обновить данные таблицы
useEffect(() => {
if (state.selectedPlan.NRN) {
//Если план выбран
if (plan.NRN) {
const loadData = async () => {
const data = await executeStored({
stored: "PKG_P8PANELS_MECHREC.FCPRODPLANSP_DEPT_DG_GET",
args: {
NFCPRODPLAN: state.selectedPlan.NRN,
NFCPRODPLAN: plan.NRN,
CORDERS: { VALUE: object2Base64XML(state.orders, { arrayNodeName: "orders" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB },
NPAGE_NUMBER: state.pageNumber,
NPAGE_SIZE: DATA_GRID_PAGE_SIZE_LARGE,
@ -76,6 +148,7 @@ const useDeptCostProdPlans = () => {
setState(pv => ({
...pv,
...data.XDATA_GRID,
init: true,
columnsDef: data.XDATA_GRID.columnsDef ? [...data.XDATA_GRID.columnsDef] : pv.columnsDef,
rows: pv.pageNumber == 1 ? [...(data.XDATA_GRID.rows || [])] : [...pv.rows, ...(data.XDATA_GRID.rows || [])],
dataLoaded: true,
@ -83,31 +156,20 @@ const useDeptCostProdPlans = () => {
morePages: (data.XDATA_GRID.rows || []).length >= DATA_GRID_PAGE_SIZE_LARGE
}));
};
//Если необходимо перезагрузить
if (state.reload) {
loadData();
}
}
}, [state.selectedPlan, state.reload, state.orders, state.pageNumber, state.dataLoaded, executeStored, SERV_DATA_TYPE_CLOB]);
//При подключении компонента к странице
useEffect(() => {
const initPlans = async () => {
const data = await executeStored({
stored: "PKG_P8PANELS_MECHREC.FCPRODPLAN_DEPT_INIT",
args: {},
respArg: "COUT",
isArray: name => name === "XFCPRODPLANS",
attributeValueProcessor: (name, val) => (name === "SPERIOD" ? undefined : val)
});
setState(pv => ({ ...pv, init: true, planList: [...(data?.XFCPRODPLANS || [])], planListLoaded: true }));
};
if (!state.init) {
initPlans();
//Если план не выбран и есть какие-то данные
if (!plan.NRN && state.dataLoaded) {
//Очищаем их
handleClear();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}, [plan.NRN, state.reload, state.orders, state.pageNumber, state.dataLoaded, executeStored, SERV_DATA_TYPE_CLOB, handleClear]);
return [state, setState];
//Возвращаем данные
return [state, setState, handleClear, handleOrderChanged, handlePagesCountChanged];
};
//Хук для таблицы маршрутных листов
@ -164,6 +226,7 @@ const useCostRouteLists = task => {
task
]);
//Возвращаем данные
return [costRouteLists, setCostRouteLists];
};
@ -223,6 +286,7 @@ const useCostRouteListsSpecs = mainRowRN => {
mainRowRN
]);
//Возвращаем данные
return [costRouteListsSpecs, setCostRouteListsSpecs];
};
@ -272,7 +336,8 @@ const useIncomFromDeps = task => {
}
}, [SERV_DATA_TYPE_CLOB, executeStored, incomFromDeps.dataLoaded, incomFromDeps.orders, incomFromDeps.pageNumber, incomFromDeps.reload, task]);
//Возвращаем данные
return [incomFromDeps, setIncomFromDeps];
};
export { useDeptCostProdPlans, useCostRouteLists, useCostRouteListsSpecs, useIncomFromDeps };
export { useDeptCostProdPlans, useDeptCostProdPlanInfo, useCostRouteLists, useCostRouteListsSpecs, useIncomFromDeps };

View File

@ -12,7 +12,7 @@ import PropTypes from "prop-types"; //Контроль свойств компо
import { Drawer, Fab, Box, List, ListItemButton, ListItemText, Typography, TextField, Link, Grid } from "@mui/material"; //Интерфейсные элементы
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Заголовок страницы
import { useDeptCostProdPlans, useFilteredPlans } from "./hooks"; //Вспомогательные хуки
import { useDeptCostProdPlans, useFilteredPlans, useDeptCostProdPlanInfo } from "./hooks"; //Вспомогательные хуки
import { P8PDataGrid, P8P_DATA_GRID_SIZE } from "../../components/p8p_data_grid"; //Таблица данных
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений
@ -34,7 +34,8 @@ const TITLE_PADDING_BOTTOM = "20px";
//Стили
const STYLES = {
PLANS_FINDER: { marginTop: "10px", marginLeft: "10px", width: "93%" },
PLANS_FILTER: { paddingTop: "20px", display: "flex", flexDirection: "column", alignItems: "center", gap: "5px" },
PLANS_FILTER_ITEM: { margin: "0px 10px", width: "93%" },
PLANS_LIST_ITEM_PRIMARY: { wordWrap: "break-word" },
PLANS_BUTTON: { position: "absolute" },
PLANS_DRAWER: {
@ -60,10 +61,18 @@ const STYLES = {
FACT_VALUE: { color: "blue" }
};
//Имена полей компонента
const SFIELD_MONTH = "month";
//------------------------------------
//Вспомогательные функции и компоненты
//------------------------------------
//Считывание текущего года и месяца в формате "YYYY-MM"
const getCurrentYearMonth = () => {
return `${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, "0")}`;
};
//Генерация представления ячейки заголовка группы
export const groupCellRender = ({ group }) => ({
cellStyle: STYLES.DATA_GRID_GROUP_CELL,
@ -88,7 +97,7 @@ const getRowBackgroudColor = row => {
};
//Генерация заливки строки исходя от значений
const dataCellRender = ({ row, columnDef, handleProdOrderClick, handleMatresCodeClick }) => {
const dataCellRender = ({ row, columnDef, onProdOrderClick, onMatresCodeClick }) => {
//Описываем общие свойства
let cellProps = { title: row[columnDef.name] };
//Описываем общий стиль
@ -127,7 +136,7 @@ const dataCellRender = ({ row, columnDef, handleProdOrderClick, handleMatresCode
cellProps,
cellStyle,
data: (
<Link component="button" variant="body2" align="left" underline="hover" onClick={() => handleProdOrderClick(row["NRN"])}>
<Link component="button" variant="body2" align="left" underline="hover" onClick={() => onProdOrderClick(row["NRN"])}>
{row[columnDef.name]}
</Link>
)
@ -139,7 +148,7 @@ const dataCellRender = ({ row, columnDef, handleProdOrderClick, handleMatresCode
cellProps,
cellStyle: STYLES.DATA_GRID_CELL_MATRES_CODE(cellStyle, row),
data: (
<Link component="button" variant="body2" align="left" underline="hover" onClick={() => handleMatresCodeClick(row["NRN"])}>
<Link component="button" variant="body2" align="left" underline="hover" onClick={() => onMatresCodeClick(row["NRN"])}>
{row[columnDef.name]}
</Link>
)
@ -149,21 +158,38 @@ const dataCellRender = ({ row, columnDef, handleProdOrderClick, handleMatresCode
};
//Список каталогов планов
const PlanList = ({ plans = [], selectedPlan, filter, setFilter, onClick } = {}) => {
const PlanList = ({ plans = [], selectedPlan, filter, onFilterChange, onClick } = {}) => {
//При изменении фильтра
const handleFilterChange = e => {
onFilterChange && onFilterChange(e);
};
//Генерация содержимого
return (
<div>
<Box sx={STYLES.PLANS_FILTER}>
<TextField
sx={STYLES.PLANS_FINDER}
name="planFilter"
sx={STYLES.PLANS_FILTER_ITEM}
name={SFIELD_MONTH}
label="Месяц"
type="month"
value={filter.month}
InputLabelProps={{ shrink: true }}
variant="standard"
fullWidth
onChange={handleFilterChange}
required={true}
></TextField>
<TextField
sx={STYLES.PLANS_FILTER_ITEM}
name="planName"
label="План"
value={filter.planName}
variant="standard"
fullWidth
onChange={event => {
setFilter(pv => ({ ...pv, planName: event.target.value }));
}}
onChange={handleFilterChange}
></TextField>
</Box>
<List>
{plans.map(p => (
<ListItemButton key={p.NRN} selected={p.NRN === selectedPlan.NRN} onClick={() => (onClick ? onClick(p) : null)}>
@ -181,7 +207,7 @@ PlanList.propTypes = {
selectedPlan: PropTypes.object,
onClick: PropTypes.func,
filter: PropTypes.object,
setFilter: PropTypes.func
onFilterChange: PropTypes.func
};
//-----------
@ -190,135 +216,135 @@ PlanList.propTypes = {
//Корневая панель производственного плана цеха
const MechRecDeptCostProdPlans = () => {
//Собственное состояние - таблица данных
const [state, setState] = useDeptCostProdPlans();
//Состояние для фильтра каталогов
const [filter, setFilter] = useState({ planName: "" });
const [filter, setFilter] = useState({ planName: "", month: getCurrentYearMonth() });
//Собственное состояние - таблица планов
const [plans, setPlans] = useDeptCostProdPlans(filter.month);
//Собственное состояние - таблица информации
const [planInfo, setPlanInfo, onPlanInfoClear, onPlanInfoOrderChanged, onPlanInfoPagesCountChanged] = useDeptCostProdPlanInfo(plans.selected);
//Массив отфильтрованных каталогов
const filteredPlanCtgls = useFilteredPlans(state.planList, filter);
const filteredPlanCtgls = useFilteredPlans(plans.rows, filter);
//Подключение к контексту сообщений
const { InlineMsgInfo } = useContext(MessagingСtx);
//Выбор плана
const selectPlan = plan => {
setState(pv => ({
setPlans(pv => ({
...pv,
showIncomeFromDeps: null,
showFcroutelst: null,
selectedPlan: plan,
showPlanList: false,
dataLoaded: false,
columnsDef: [],
orders: null,
rows: [],
reload: true,
pageNumber: 1,
morePages: true
selected: plan,
showPlanList: false
}));
onPlanInfoClear();
};
//Сброс выбора плана
const unselectPlan = () =>
setState(pv => ({
const unselectPlan = () => {
setPlans(pv => ({
...pv,
showIncomeFromDeps: null,
showFcroutelst: null,
selectedPlan: {},
showPlanList: false,
dataLoaded: false,
columnsDef: [],
orders: null,
rows: [],
reload: true,
pageNumber: 1,
morePages: true
selected: {},
showPlanList: false
}));
onPlanInfoClear();
};
//Обработка нажатия на элемент в списке планов
const handlePlanClick = plan => {
if (state.selectedPlan.NRN != plan.NRN) selectPlan(plan);
if (plans.selected.NRN != plan.NRN) selectPlan(plan);
else unselectPlan();
};
//При изменении состояния сортировки
const handleOrderChanged = ({ orders }) => setState(pv => ({ ...pv, orders: [...orders], pageNumber: 1, reload: true }));
//При изменении состояния сортировки информации плана
const handlePlanInfoOrderChanged = ({ orders }) => onPlanInfoOrderChanged({ orders });
//При изменении количества отображаемых страниц
const handlePagesCountChanged = () => setState(pv => ({ ...pv, pageNumber: pv.pageNumber + 1, reload: true }));
//При изменении количества отображаемых страниц информации плана
const handlePlanInfoPagesCountChanged = () => onPlanInfoPagesCountChanged();
//При нажатии на "Заказ"
const handleProdOrderClick = planSp => {
setState(pv => ({ ...pv, showIncomeFromDeps: planSp }));
setPlanInfo(pv => ({ ...pv, showIncomeFromDeps: planSp }));
};
//При нажатии на "Обозначение"
const handleMatresCodeClick = planSp => {
setState(pv => ({ ...pv, showFcroutelst: planSp }));
setPlanInfo(pv => ({ ...pv, showFcroutelst: planSp }));
};
//При изменении фильтров
const handleFilterChange = e =>
setFilter(pv => ({ ...pv, [e.target.name]: e.target.name === SFIELD_MONTH && !e.target.value ? getCurrentYearMonth() : e.target.value }));
//Генерация содержимого
return (
<Box p={2}>
<Fab variant="extended" sx={STYLES.PLANS_BUTTON} onClick={() => setState(pv => ({ ...pv, showPlanList: !pv.showPlanList }))}>
<Fab variant="extended" sx={STYLES.PLANS_BUTTON} onClick={() => setPlans(pv => ({ ...pv, showPlanList: !pv.showPlanList }))}>
Планы
</Fab>
<Drawer
anchor={"left"}
open={state.showPlanList}
onClose={() => setState(pv => ({ ...pv, showPlanList: false }))}
open={plans.showPlanList}
onClose={() => setPlans(pv => ({ ...pv, showPlanList: false }))}
sx={STYLES.PLANS_DRAWER}
>
<PlanList
plans={filteredPlanCtgls}
selectedPlan={state.selectedPlan}
selectedPlan={plans.selected}
filter={filter}
setFilter={setFilter}
onFilterChange={handleFilterChange}
onClick={handlePlanClick}
/>
</Drawer>
<Grid container>
<Grid item xs={12}>
<Box display="flex" justifyContent="center" alignItems="center">
{state.dataLoaded ? (
state.rows.length === 0 ? (
{planInfo.dataLoaded ? (
planInfo.rows.length === 0 ? (
<InlineMsgInfo okBtn={false} text={"В плане отсутствуют записи спецификации"} />
) : (
<Box sx={STYLES.CONTAINER}>
<Typography sx={STYLES.TITLE} variant={"h6"}>
{`Производственный план цеха №${state.selectedPlan.SSUBDIV} на ${state.selectedPlan.SPERIOD}`}
{`Производственный план цеха №${plans.selected.SSUBDIV} на ${plans.selected.SPERIOD}`}
</Typography>
<Box>
<P8PDataGrid
{...P8P_DATA_GRID_CONFIG_PROPS}
containerComponentProps={{ elevation: 6, sx: STYLES.DATA_GRID_CONTAINER }}
fixedHeader={state.fixedHeader}
fixedColumns={state.fixedColumns}
columnsDef={state.columnsDef}
rows={state.rows}
fixedHeader={planInfo.fixedHeader}
fixedColumns={planInfo.fixedColumns}
columnsDef={planInfo.columnsDef}
rows={planInfo.rows}
size={P8P_DATA_GRID_SIZE.MEDIUM}
morePages={state.morePages}
reloading={state.reload}
onOrderChanged={handleOrderChanged}
onPagesCountChanged={handlePagesCountChanged}
dataCellRender={prms => dataCellRender({ ...prms, handleProdOrderClick, handleMatresCodeClick })}
morePages={planInfo.morePages}
reloading={planInfo.reload}
onOrderChanged={handlePlanInfoOrderChanged}
onPagesCountChanged={handlePlanInfoPagesCountChanged}
dataCellRender={prms =>
dataCellRender({
...prms,
onProdOrderClick: handleProdOrderClick,
onMatresCodeClick: handleMatresCodeClick
})
}
groupCellRender={groupCellRender}
/>
</Box>
</Box>
)
) : !state.selectedPlan.NRN ? (
) : !plans.selected.NRN ? (
<InlineMsgInfo okBtn={false} text={"Укажите план для отображения спецификаций"} />
) : null}
</Box>
</Grid>
</Grid>
{state.showIncomeFromDeps ? (
<IncomFromDepsDataGridDialog task={state.showIncomeFromDeps} onClose={() => handleProdOrderClick(null)} />
{planInfo.showIncomeFromDeps ? (
<IncomFromDepsDataGridDialog task={planInfo.showIncomeFromDeps} onClose={() => handleProdOrderClick(null)} />
) : null}
{planInfo.showFcroutelst ? (
<CostRouteListsDataGridDialog task={planInfo.showFcroutelst} onClose={() => handleMatresCodeClick(null)} />
) : null}
{state.showFcroutelst ? <CostRouteListsDataGridDialog task={state.showFcroutelst} onClose={() => handleMatresCodeClick(null)} /> : null}
</Box>
);
};

View File

@ -150,6 +150,7 @@ create or replace package PKG_P8PANELS_MECHREC as
/* Инициализация записей раздела "Планы и отчеты производства изделий" */
procedure FCPRODPLAN_DEPT_INIT
(
SMONTH in varchar2, -- Месяц отбора (в формате "YYYY-MM")
COUT out clob -- Список записей раздела "Планы и отчеты производства изделий"
);
@ -4316,6 +4317,7 @@ create or replace package body PKG_P8PANELS_MECHREC as
/* Инициализация записей раздела "Планы и отчеты производства изделий" */
procedure FCPRODPLAN_DEPT_INIT
(
SMONTH in varchar2, -- Месяц отбора (в формате "YYYY-MM")
COUT out clob -- Список записей раздела "Планы и отчеты производства изделий"
)
is
@ -4327,7 +4329,7 @@ create or replace package body PKG_P8PANELS_MECHREC as
/* Считываем версию контрагентов */
FIND_VERSION_BY_COMPANY(NCOMPANY => NCOMPANY, SUNITCODE => 'AGNLIST', NVERSION => NVERSION);
/* Определяем период записей */
P_FIRST_LAST_DAY(DCALCDATE => sysdate, DBGNDATE => DDATE_FROM, DENDDATE => DDATE_TO);
P_FIRST_LAST_DAY(DCALCDATE => TO_DATE(SMONTH, 'yyyy-mm'), DBGNDATE => DDATE_FROM, DENDDATE => DDATE_TO);
/* Начинаем формирование XML */
PKG_XFAST.PROLOGUE(ITYPE => PKG_XFAST.CONTENT_);
/* Открываем корень */

4
dist/p8-panels.js vendored

File diff suppressed because one or more lines are too long