Compare commits
No commits in common. "43acce3b51023419d0f7c7ccf4433be34ac80289" and "32aa59b039b1be19de0cfb4240cedc020038011c" have entirely different histories.
43acce3b51
...
32aa59b039
@ -10,24 +10,6 @@ const remainingBookingKeys = [
|
|||||||
"data.q4.residual_after_booking",
|
"data.q4.residual_after_booking",
|
||||||
];
|
];
|
||||||
|
|
||||||
// Требование №1 работает для всех форм и листов, кроме CAP и OPER формы 4.
|
|
||||||
const remainingBookingRuleScope = {
|
|
||||||
excludedScopes: [
|
|
||||||
{ formType: "FORM_4", sheetName: "CAP" },
|
|
||||||
{ formType: "FORM_4", sheetName: "OPER" },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
// Для листов CAP и OPER формы 4 требование №1 проверяет только остаток за 4 квартал.
|
|
||||||
const remainingBooking4FormKeys = ["data.q4.residual_after_booking"];
|
|
||||||
|
|
||||||
const remainingBooking4FormRuleScope = {
|
|
||||||
includedScopes: [
|
|
||||||
{ formType: "FORM_4", sheetName: "CAP" },
|
|
||||||
{ formType: "FORM_4", sheetName: "OPER" },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
// Требование №2. Контроль столбца «Текущие корректировки = 0».
|
// Требование №2. Контроль столбца «Текущие корректировки = 0».
|
||||||
const zeroValueRequirementKeys = [
|
const zeroValueRequirementKeys = [
|
||||||
"data.q1.adj_current",
|
"data.q1.adj_current",
|
||||||
@ -36,7 +18,7 @@ const zeroValueRequirementKeys = [
|
|||||||
"data.q4.adj_current",
|
"data.q4.adj_current",
|
||||||
];
|
];
|
||||||
|
|
||||||
// Бронь <= Остаток после брони для формы 2.
|
//Бронь <= Остаток после брони
|
||||||
const bookingLessBalance = [
|
const bookingLessBalance = [
|
||||||
["data.q1.booking", "data.q1.residual_after_booking"],
|
["data.q1.booking", "data.q1.residual_after_booking"],
|
||||||
["data.q2.booking", "data.q2.residual_after_booking"],
|
["data.q2.booking", "data.q2.residual_after_booking"],
|
||||||
@ -51,18 +33,15 @@ const bookingLessBalanceByColumn = new Map(
|
|||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Экономия <= Всего для форм 1 и 4.
|
//Бронь <= Остаток после брони для листов кв.П и Опер 4 формы
|
||||||
const economyLessTotal = [
|
const bookingLessBalance4From = [
|
||||||
["data.q1.economy", "data.q1.total"],
|
["data.q4.booking", "data.q4.residual_after_booking"],
|
||||||
["data.q2.economy", "data.q2.total"],
|
|
||||||
["data.q3.economy", "data.q3.total"],
|
|
||||||
["data.q4.economy", "data.q4.total"],
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const economyLessTotalByColumn = new Map(
|
const bookingLessBalance4FormByColumn = new Map(
|
||||||
economyLessTotal.flatMap(([economyKey, totalKey]) => [
|
bookingLessBalance.flatMap(([bookingKey, balanceKey]) => [
|
||||||
[economyKey, { economyKey, totalKey }],
|
[bookingKey, { bookingKey, balanceKey }],
|
||||||
[totalKey, { economyKey, totalKey }],
|
[balanceKey, { bookingKey, balanceKey }],
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -80,13 +59,17 @@ const transferLessTotalByColumn = new Map(
|
|||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Область действия всех правил. Пары можно добавлять, удалять и изменять.
|
||||||
const bookingLessBalanceRuleScope = {
|
const bookingLessBalanceRuleScope = {
|
||||||
includedScopes: [{ formType: "FORM_2" }],
|
includedScopes: [],
|
||||||
|
excludedScopes: [
|
||||||
|
{ formType: "FORM_4", sheetName: "CAP" },
|
||||||
|
{ formType: "FORM_4", sheetName: "OPER" },
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const validationRules = [
|
export const validationRules = [
|
||||||
{
|
{
|
||||||
...remainingBookingRuleScope,
|
|
||||||
id: "remaining-booking",
|
id: "remaining-booking",
|
||||||
columnKeys: remainingBookingKeys,
|
columnKeys: remainingBookingKeys,
|
||||||
message: "Сумма бронирования превышает сумму, предусмотренную сметой",
|
message: "Сумма бронирования превышает сумму, предусмотренную сметой",
|
||||||
@ -97,18 +80,6 @@ export const validationRules = [
|
|||||||
Number.isFinite(Number(value)) &&
|
Number.isFinite(Number(value)) &&
|
||||||
Number(value) < 0,
|
Number(value) < 0,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
...remainingBooking4FormRuleScope,
|
|
||||||
id: "remaining-booking-4-form",
|
|
||||||
columnKeys: remainingBooking4FormKeys,
|
|
||||||
message: "Сумма бронирования превышает сумму, предусмотренную сметой",
|
|
||||||
isInvalid: (value) =>
|
|
||||||
value !== null &&
|
|
||||||
value !== undefined &&
|
|
||||||
value !== "" &&
|
|
||||||
Number.isFinite(Number(value)) &&
|
|
||||||
Number(value) < 0,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: "current-adjustments-zero",
|
id: "current-adjustments-zero",
|
||||||
columnKeys: zeroValueRequirementKeys,
|
columnKeys: zeroValueRequirementKeys,
|
||||||
@ -152,32 +123,31 @@ export const validationRules = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
includedScopes: [{ formType: "FORM_1" }, { formType: "FORM_4" }],
|
id: "booking-less-balance-4-form",
|
||||||
id: "economy-less-total",
|
columnKeys: bookingLessBalance4From.flat(),
|
||||||
columnKeys: economyLessTotal.flat(),
|
errorColumnKeys: bookingLessBalance4From.map(([bookingKey]) => bookingKey),
|
||||||
errorColumnKeys: economyLessTotal.map(([economyKey]) => economyKey),
|
message: "Сумма бронирования превышает остаток после бронирования",
|
||||||
message: "Экономия должна быть меньше значения «Всего»",
|
|
||||||
isInvalid: (_value, row, columnKey) => {
|
isInvalid: (_value, row, columnKey) => {
|
||||||
const pair = economyLessTotalByColumn.get(columnKey);
|
const pair = bookingLessBalance4FormByColumn.get(columnKey);
|
||||||
if (!pair) return false;
|
if (!pair) return false;
|
||||||
|
|
||||||
const economy = pair.economyKey
|
const booking = pair.bookingKey
|
||||||
.split(".")
|
.split(".")
|
||||||
.reduce((value, key) => value?.[key], row);
|
.reduce((value, key) => value?.[key], row);
|
||||||
const total = pair.totalKey
|
const balance = pair.balanceKey
|
||||||
.split(".")
|
.split(".")
|
||||||
.reduce((value, key) => value?.[key], row);
|
.reduce((value, key) => value?.[key], row);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
economy !== null &&
|
booking !== null &&
|
||||||
economy !== undefined &&
|
booking !== undefined &&
|
||||||
economy !== "" &&
|
booking !== "" &&
|
||||||
total !== null &&
|
balance !== null &&
|
||||||
total !== undefined &&
|
balance !== undefined &&
|
||||||
total !== "" &&
|
balance !== "" &&
|
||||||
Number.isFinite(Number(economy)) &&
|
Number.isFinite(Number(booking)) &&
|
||||||
Number.isFinite(Number(total)) &&
|
Number.isFinite(Number(balance)) &&
|
||||||
Number(economy) > Number(total)
|
Number(booking) > Number(balance)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -555,14 +555,14 @@ export const TransitionSvg = ({ size = 16 }) => (
|
|||||||
|
|
||||||
export const ProjectSvg = ({ size = 16 }) => (
|
export const ProjectSvg = ({ size = 16 }) => (
|
||||||
<svg width={size} height={size} viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
<svg width={size} height={size} viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
||||||
<path d="M2.7 3.2h4.1l1 1.3h5.5c.7 0 1.2.5 1.2 1.2v6.6c0 .7-.5 1.2-1.2 1.2H2.7c-.7 0-1.2-.5-1.2-1.2V4.4c0-.7.5-1.2 1.2-1.2Z" stroke="#258141" strokeWidth="1.15" strokeLinejoin="round" /><path d="M4.2 7.2h7.6M4.2 9.5h5.4" stroke="#258141" strokeWidth="1.15" strokeLinecap="round">
|
<path d="M2.7 3.2h4.1l1 1.3h5.5c.7 0 1.2.5 1.2 1.2v6.6c0 .7-.5 1.2-1.2 1.2H2.7c-.7 0-1.2-.5-1.2-1.2V4.4c0-.7.5-1.2 1.2-1.2Z" stroke="#258141" stroke-width="1.15" strokeLinejoin="round" /><path d="M4.2 7.2h7.6M4.2 9.5h5.4" stroke="#258141" stroke-width="1.15" stroke-linecap="round">
|
||||||
</path>
|
</path>
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const SummarySvg = ({ size = 16 }) => (
|
export const SummarySvg = ({ size = 16 }) => (
|
||||||
<svg width={size} height={size} viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
<svg width={size} height={size} viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
||||||
<path d="M2.5 13.3V2.7M2.5 13.3h11" stroke="#99A1AF" strokeWidth="1.15" strokeLinecap="round" /><path d="M4.6 10.6V7.9M7.9 10.6V4.8M11.2 10.6V6.4" stroke="#99A1AF" strokeWidth="1.45" strokeLinecap="round">
|
<path d="M2.5 13.3V2.7M2.5 13.3h11" stroke="#99A1AF" stroke-width="1.15" stroke-linecap="round" /><path d="M4.6 10.6V7.9M7.9 10.6V4.8M11.2 10.6V6.4" stroke="#99A1AF" stroke-width="1.45" stroke-linecap="round">
|
||||||
</path>
|
</path>
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -39,7 +39,7 @@ function DictVspPage() {
|
|||||||
if (!user) return;
|
if (!user) return;
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
const isAdmin = user.role_id === ROLES_NAME_ID.admin;
|
const isAdmin = user.role_id == ROLES_NAME_ID.admin;
|
||||||
const [vspResponse, sspResponse] = await Promise.all([
|
const [vspResponse, sspResponse] = await Promise.all([
|
||||||
DictVspApi.get(),
|
DictVspApi.get(),
|
||||||
isAdmin ? SspApi.getAll({ is_active: true }) : Promise.resolve(null),
|
isAdmin ? SspApi.getAll({ is_active: true }) : Promise.resolve(null),
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Box, CircularProgress, Paper, Typography } from '@mui/material';
|
import { Box, CircularProgress, Paper, Typography } from '@mui/material';
|
||||||
import { MaterialReactTable, useMaterialReactTable } from 'material-react-table';
|
import { MaterialReactTable, useMaterialReactTable } from 'material-react-table';
|
||||||
import { useEffect, useMemo, useState, useCallback } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { ProjectsApi } from '../../api/projects';
|
import { ProjectsApi } from '../../api/projects';
|
||||||
@ -15,8 +15,6 @@ import { createFirstColumns, secondColumns } from './columns';
|
|||||||
import EditModal from './components/EditModal/EditModal';
|
import EditModal from './components/EditModal/EditModal';
|
||||||
import TableFilters from './components/TableFilters/TableFilters'; // Импортируем компонент фильтров
|
import TableFilters from './components/TableFilters/TableFilters'; // Импортируем компонент фильтров
|
||||||
import { handleNavigateClick } from './utils/tableHandlers';
|
import { handleNavigateClick } from './utils/tableHandlers';
|
||||||
import { useAuth } from '../../app/context/AuthProvider';
|
|
||||||
import { ROLES_NAME_ID } from '../../constants/constants';
|
|
||||||
|
|
||||||
const SummaryPage = () => {
|
const SummaryPage = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@ -35,49 +33,32 @@ const SummaryPage = () => {
|
|||||||
const [createModalOpen, setCreateModalOpen] = useState(false);
|
const [createModalOpen, setCreateModalOpen] = useState(false);
|
||||||
const [projectToDelete, setProjectToDelete] = useState(null);
|
const [projectToDelete, setProjectToDelete] = useState(null);
|
||||||
const [isDeleting, setIsDeleting] = useState(false);
|
const [isDeleting, setIsDeleting] = useState(false);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
|
||||||
|
|
||||||
const { user } = useAuth();
|
|
||||||
|
|
||||||
// Состояния для фильтров
|
// Состояния для фильтров
|
||||||
const [selectedBranch, setSelectedBranch] = useState('');
|
const [selectedBranch, setSelectedBranch] = useState('');
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
|
|
||||||
const loadData = useCallback(async () => {
|
|
||||||
if (!user) return;
|
|
||||||
setIsLoading(true);
|
|
||||||
try {
|
|
||||||
const isAdmin = user.role_id === ROLES_NAME_ID.admin;
|
|
||||||
|
|
||||||
if (isAdmin) {
|
|
||||||
const sspResponse = await (
|
|
||||||
SspApi.getAll({ is_active: true })
|
|
||||||
);
|
|
||||||
if (!sspResponse.success) {
|
|
||||||
toast.error('Ошибка загрузки списка ССП');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setOrgUnits(sspResponse.result);
|
|
||||||
setOrgUnitNames(
|
|
||||||
Object.fromEntries((sspResponse.result || []).map((orgUnit) => [orgUnit.id, orgUnit.title])),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
setOrgUnits(user.org_units ?? []);
|
|
||||||
setOrgUnitNames(
|
|
||||||
Object.fromEntries((user.org_units || []).map((orgUnit) => [orgUnit.id, orgUnit.title])),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error loading data:', error);
|
|
||||||
toast.error(error.response?.data?.message || 'Ошибка загрузки данных');
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
}, [user]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadData();
|
let active = true;
|
||||||
}, [loadData]);
|
const loadOrgUnits = async () => {
|
||||||
|
try {
|
||||||
|
const response = await SspApi.getAll({ is_active: true });
|
||||||
|
if (active) {
|
||||||
|
setOrgUnits(response.result || []);
|
||||||
|
setOrgUnitNames(
|
||||||
|
Object.fromEntries((response.result || []).map((orgUnit) => [orgUnit.id, orgUnit.title])),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (requestError) {
|
||||||
|
console.error('Error loading SSP/RF names:', requestError);
|
||||||
|
if (active) toast.error('Не удалось загрузить список ССП/РФ');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
loadOrgUnits();
|
||||||
|
return () => {
|
||||||
|
active = false;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
const createProjectConfig = useMemo(() => ({
|
const createProjectConfig = useMemo(() => ({
|
||||||
...DEFAULT_PROJECT_CONFIG,
|
...DEFAULT_PROJECT_CONFIG,
|
||||||
|
|||||||
@ -1,48 +1,27 @@
|
|||||||
import { Search } from '@mui/icons-material';
|
import { Search } from '@mui/icons-material';
|
||||||
import { Autocomplete, Box, Chip, CircularProgress, Paper, TextField } from '@mui/material';
|
import { Autocomplete, Box, Chip, CircularProgress, Paper, TextField } from '@mui/material';
|
||||||
import { useEffect, useState, useCallback } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { SspApi } from '../../../../api/ssp';
|
import { SspApi } from '../../../../api/ssp';
|
||||||
import { useAuth } from '../../../../app/context/AuthProvider';
|
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
import { ROLES_NAME_ID } from '../../../../constants/constants';
|
|
||||||
|
|
||||||
const TableFilters = ({ onBranchChange, onSearchChange, selectedBranch, searchQuery }) => {
|
const TableFilters = ({ onBranchChange, onSearchChange, selectedBranch, searchQuery }) => {
|
||||||
const [branches, setBranches] = useState([]);
|
const [branches, setBranches] = useState([]);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const { user } = useAuth();
|
|
||||||
|
|
||||||
const loadData = useCallback(async () => {
|
|
||||||
if (!user) return;
|
|
||||||
setIsLoading(true);
|
|
||||||
try {
|
|
||||||
const isAdmin = user.role_id === ROLES_NAME_ID.admin;
|
|
||||||
|
|
||||||
if (isAdmin) {
|
|
||||||
const sspResponse = await (
|
|
||||||
SspApi.getAll({ is_active: true })
|
|
||||||
);
|
|
||||||
if (!sspResponse.success) {
|
|
||||||
toast.error('Ошибка загрузки списка ССП');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setBranches(sspResponse.result);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
setBranches(user.org_units ?? []);
|
|
||||||
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error loading data:', error);
|
|
||||||
toast.error(error.response?.data?.message || 'Ошибка загрузки данных');
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
}, [user]);
|
|
||||||
|
|
||||||
|
|
||||||
|
// Загружаем филиалы из API
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadData();
|
const loadBranches = async () => {
|
||||||
}, [loadData]);
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const data = await SspApi.getAll({ is_active: true });
|
||||||
|
setBranches(data.result);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading branches:', error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
loadBranches();
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper
|
<Paper
|
||||||
@ -67,7 +46,7 @@ const TableFilters = ({ onBranchChange, onSearchChange, selectedBranch, searchQu
|
|||||||
flex: '1 1 auto',
|
flex: '1 1 auto',
|
||||||
}}
|
}}
|
||||||
options={branches}
|
options={branches}
|
||||||
loading={isLoading}
|
loading={loading}
|
||||||
getOptionLabel={(option) => option.title || ''}
|
getOptionLabel={(option) => option.title || ''}
|
||||||
getOptionKey={(option) => option.id}
|
getOptionKey={(option) => option.id}
|
||||||
value={selectedBranch || null}
|
value={selectedBranch || null}
|
||||||
@ -86,7 +65,7 @@ const TableFilters = ({ onBranchChange, onSearchChange, selectedBranch, searchQu
|
|||||||
...InputProps,
|
...InputProps,
|
||||||
endAdornment: (
|
endAdornment: (
|
||||||
<>
|
<>
|
||||||
{isLoading && <CircularProgress color='inherit' size={20} />}
|
{loading && <CircularProgress color='inherit' size={20} />}
|
||||||
{InputProps?.endAdornment}
|
{InputProps?.endAdornment}
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user