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 ;
})]}
+ {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))}