From 7ca972a96ae035e721c4aec749914538186df48c Mon Sep 17 00:00:00 2001 From: PotapovaA Date: Thu, 20 Aug 2026 13:05:54 +0300 Subject: [PATCH] updating access for projects --- web/src/constants/projectConfig.jsx | 14 ++++---- web/src/pages/FormPage.jsx | 2 +- web/src/pages/ProjectPages/SummaryPage.jsx | 4 ++- web/src/pages/ProjectPages/columns.js | 14 ++++++++ .../components/EditModal/EditModal.jsx | 9 ++++- .../ProjectPages/constants/fieldAccess.js | 33 ++++++++++++++++--- .../pages/ProjectsPage/ModalCreateProject.jsx | 10 +++--- 7 files changed, 68 insertions(+), 18 deletions(-) diff --git a/web/src/constants/projectConfig.jsx b/web/src/constants/projectConfig.jsx index fbe3b07..4c083cf 100644 --- a/web/src/constants/projectConfig.jsx +++ b/web/src/constants/projectConfig.jsx @@ -12,14 +12,14 @@ export const DEFAULT_PROJECT_CONFIG = { defaultValue: null, min: 0, step: 1, - required_field: false, + required_field: true, }, 'Тип проекта развития': { type: 'multiselect', options: ['Открытие ВСП', 'Закрытие ВСП', 'Переезд ВСП', 'Реновация РФ', 'Открытие УРМ'], placeholder: 'Выберите тип проекта', defaultValue: null, - required_field: false, + required_field: true, }, 'Название проекта': { type: 'text', @@ -29,16 +29,17 @@ export const DEFAULT_PROJECT_CONFIG = { }, 'Формат ВСП': { type: 'multiselect', - options: ['Фланговый', 'Типовой', 'Розничный', 'МСБ', 'Лёгкий', 'Мини', 'Розничный-киоск', 'МБО', 'Офис самообслуживания', 'Другое'], + options: ['Флагманский', 'Типовой', 'Розничный', 'МСБ', 'Лёгкий', 'Мини', 'Розничный-киоск', 'МБО', 'Офис самообслуживания', 'Другое'], placeholder: 'Выберите формат ВСП', defaultValue: null, - required_field: false, + required_field: true, }, 'Адрес объекта': { type: 'text', placeholder: 'Введите адрес объекта', defaultValue: '', - required_field: false, + required_field: true, + }, 'Целевая численность, чел.': { type: 'number', @@ -53,7 +54,8 @@ export const DEFAULT_PROJECT_CONFIG = { options: ['Собственность', 'Аренда', 'Субаренда'], placeholder: 'Выберите тип размещения', defaultValue: null, - required_field: false, + required_field: true, + }, 'Площадь размещения, м2': { type: 'number', diff --git a/web/src/pages/FormPage.jsx b/web/src/pages/FormPage.jsx index 1759b6a..c226003 100644 --- a/web/src/pages/FormPage.jsx +++ b/web/src/pages/FormPage.jsx @@ -135,7 +135,7 @@ export default function FormPage() { 'Формат ВСП': { type: 'multiselect', options: [ - 'Фланговый', + 'Флагманский', 'Типовой', 'Розничный', 'МСБ', diff --git a/web/src/pages/ProjectPages/SummaryPage.jsx b/web/src/pages/ProjectPages/SummaryPage.jsx index 4e63921..6eb7390 100644 --- a/web/src/pages/ProjectPages/SummaryPage.jsx +++ b/web/src/pages/ProjectPages/SummaryPage.jsx @@ -202,11 +202,13 @@ const SummaryPage = () => { const onEdit = (row) => { const isSmeta = row.depth > 0; - const projectId = isSmeta ? row.getParentRow()?.original?.id : row.original.id; + const parentProject = isSmeta ? row.getParentRow()?.original : row.original; + const projectId = parentProject?.id; setSelectedRowData({ ...row.original, _rowType: isSmeta ? 'smeta' : 'project', _projectId: projectId, + _projectFundingByKoDecision: parentProject?.funding_by_ko_decision, }); setEditModalOpen(true); }; diff --git a/web/src/pages/ProjectPages/columns.js b/web/src/pages/ProjectPages/columns.js index 87ee8bd..24ab02a 100644 --- a/web/src/pages/ProjectPages/columns.js +++ b/web/src/pages/ProjectPages/columns.js @@ -1,4 +1,5 @@ import { Box, IconButton, Tooltip } from '@mui/material'; +import { differenceInMonths, isValid, parseISO } from 'date-fns'; import { Back, EditPenSvg, TrashSvg } from '../../components/common/icons/icons'; import StatusBadge from './components/StatusBadge/StatusBadge'; import { DEVELOMENT_BLOCK, FUNDING_BY_DECISION } from './constants'; @@ -63,6 +64,19 @@ export const createFirstColumns = (handlers) => { header: 'Дата открытия / переезда / закрытия', size: 160, }, + { + id: 'project_implementation_months', + accessorFn: (row) => { + if (!Array.isArray(row.sub_rows) || !row.board_decision_date) return null; + + const boardDecisionDate = parseISO(row.board_decision_date); + if (!isValid(boardDecisionDate)) return null; + + return differenceInMonths(new Date(), boardDecisionDate); + }, + header: 'Время реализации проекта в месяцах', + size: 180, + }, { accessorKey: 'project_type', header: 'Тип проекта', diff --git a/web/src/pages/ProjectPages/components/EditModal/EditModal.jsx b/web/src/pages/ProjectPages/components/EditModal/EditModal.jsx index 76680c6..d679569 100644 --- a/web/src/pages/ProjectPages/components/EditModal/EditModal.jsx +++ b/web/src/pages/ProjectPages/components/EditModal/EditModal.jsx @@ -4,7 +4,7 @@ import React from 'react'; import { useAuth } from '../../../../app/context/AuthProvider'; import { DangerOutlinedButton, PrimaryButton } from '../../../../components/common/Buttons/Buttons'; import { DEVELOMENT_BLOCK, FUNDING_BY_DECISION } from '../../constants'; -import { canEditFirstTableField, isFirstTableFieldDependencySatisfied } from '../../constants/fieldAccess'; +import { canEditFirstTableField, hasReserveValuesInChildSmetas, isFirstTableFieldDependencySatisfied } from '../../constants/fieldAccess'; const EditModal = ({ open, onClose, rowData, onSave, isSaving = false, orgUnitNames = {} }) => { const { user } = useAuth(); @@ -80,8 +80,10 @@ const EditModal = ({ open, onClose, rowData, onSave, isSaving = false, orgUnitNa // Компонент для поля ввода const renderField = (field) => { const rawValue = formData[field.key]; + const isFundingByDecisionLocked = field.key === 'funding_by_ko_decision' && hasReserveValuesInChildSmetas(formData); const isFieldDisabled = field.disabled || + isFundingByDecisionLocked || !canEditFirstTableField(field.key, user?.role_id) || !isFirstTableFieldDependencySatisfied(field.key, formData); const fieldValue = field.valueType === 'boolean' && rawValue !== null && rawValue !== undefined @@ -143,6 +145,11 @@ const EditModal = ({ open, onClose, rowData, onSave, isSaving = false, orgUnitNa return {label}; })]} + {isFundingByDecisionLocked && ( + + Поле нельзя изменить: в одной из дочерних смет заполнено значение «Из резерва Банка в смету ПРРС АХР» или «Из резерва Банка в смету ПРРС КВ». + + )} ); }; diff --git a/web/src/pages/ProjectPages/constants/fieldAccess.js b/web/src/pages/ProjectPages/constants/fieldAccess.js index 9f0782e..f4f7f66 100644 --- a/web/src/pages/ProjectPages/constants/fieldAccess.js +++ b/web/src/pages/ProjectPages/constants/fieldAccess.js @@ -11,14 +11,14 @@ const ALL_ROLES = [ // Матрица доступа к полям первой таблицы. export const FIRST_TABLE_FIELD_ACCESS = { name: ADMIN_ROLES, - org_unit_id: ADMIN_ROLES, + org_unit_id: ALL_ROLES, status: DFIP_ROLES, technical_number: [], object_address: ALL_ROLES, vsp_format: ALL_ROLES, - placement_type: ADMIN_ROLES, - staff_count: ADMIN_ROLES, - total_area: ADMIN_ROLES, + placement_type: ALL_ROLES, + staff_count: ALL_ROLES, + total_area: ALL_ROLES, project_type: ALL_ROLES, krf_decision_date: ALL_ROLES, fk_decision_date: ALL_ROLES, @@ -41,10 +41,33 @@ export const FIRST_TABLE_FIELD_DEPENDENCIES = { board_decision_date: "fk_decision_date", }; +export const FIRST_TABLE_FIELD_VALUE_REQUIREMENTS = { + reserve_to_prrs_ahr: { + fieldKey: "_projectFundingByKoDecision", + value: "bank_reserve", + }, + reserve_to_prrs_kv: { + fieldKey: "_projectFundingByKoDecision", + value: "bank_reserve", + }, +}; + export const canEditFirstTableField = (fieldKey, roleId) => FIRST_TABLE_FIELD_ACCESS[fieldKey]?.includes(roleId) ?? false; +const isFilled = (value) => value !== null && value !== undefined && value !== ""; + +export const hasReserveValuesInChildSmetas = (project) => + (project?.sub_rows || []).some( + (smeta) => isFilled(smeta.reserve_to_prrs_ahr) || isFilled(smeta.reserve_to_prrs_kv), + ); + export const isFirstTableFieldDependencySatisfied = (fieldKey, rowData) => { const dependencyKey = FIRST_TABLE_FIELD_DEPENDENCIES[fieldKey]; - return !dependencyKey || Boolean(rowData?.[dependencyKey]); + if (dependencyKey && !rowData?.[dependencyKey]) return false; + + const valueRequirement = FIRST_TABLE_FIELD_VALUE_REQUIREMENTS[fieldKey]; + if (valueRequirement && rowData?.[valueRequirement.fieldKey] !== valueRequirement.value) return false; + + return true; }; diff --git a/web/src/pages/ProjectsPage/ModalCreateProject.jsx b/web/src/pages/ProjectsPage/ModalCreateProject.jsx index 0294833..f84bfba 100644 --- a/web/src/pages/ProjectsPage/ModalCreateProject.jsx +++ b/web/src/pages/ProjectsPage/ModalCreateProject.jsx @@ -120,7 +120,7 @@ export const ModalCreateProject = ({ switch (config.type) { case 'multiselect': return ( - + + + {Object.entries(config).map(([fieldName, config]) => renderField(fieldName, config))}