From df6ea391945ccfbda70a720299495823cc2de40c Mon Sep 17 00:00:00 2001 From: PotapovaA Date: Thu, 13 Aug 2026 17:43:14 +0300 Subject: [PATCH] =?UTF-8?q?=D0=B8=D1=81=D0=BA=D0=BB=D1=8E=D1=87=D0=B8?= =?UTF-8?q?=D0=BB=D0=B0=201-3=20=D0=BA=D0=B2=20=D0=B4=D0=BB=D1=8F=20=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=B5=D1=80=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RealtimeTable/constants/validation.js | 69 +++++++++++++++++-- .../RealtimeTable/hooks/useValidationRules.js | 16 +++-- .../RealtimeTable/utils/validationUtils.js | 15 ++++ 3 files changed, 89 insertions(+), 11 deletions(-) diff --git a/web/src/components/RealtimeTable/constants/validation.js b/web/src/components/RealtimeTable/constants/validation.js index d8eec05..ecb7b34 100644 --- a/web/src/components/RealtimeTable/constants/validation.js +++ b/web/src/components/RealtimeTable/constants/validation.js @@ -33,13 +33,25 @@ const bookingLessBalanceByColumn = new Map( ]), ); +//Бронь <= Остаток после брони для листов КВП и Опер 4 формы +const bookingLessBalance4From = [ + ["data.q4.booking", "data.q4.residual_after_booking"], +]; + +const bookingLessBalance4FormByColumn = new Map( + bookingLessBalance.flatMap(([bookingKey, balanceKey]) => [ + [bookingKey, { bookingKey, balanceKey }], + [balanceKey, { bookingKey, balanceKey }], + ]), +); + //Перенос в фонд экономии <= Всего const transferLessTotal = [ ["data.q1.transfer_econ", "data.q1.total"], ["data.q2.transfer_econ", "data.q2.total"], ["data.q3.transfer_econ", "data.q3.total"], ["data.q4.transfer_econ", "data.q4.total"], -] +]; const transferLessTotalByColumn = new Map( transferLessTotal.flatMap(([transferKey, totalKey]) => [ [transferKey, { transferKey: transferKey, totalKey: totalKey }], @@ -47,6 +59,15 @@ const transferLessTotalByColumn = new Map( ]), ); +// Область действия всех правил. Пары можно добавлять, удалять и изменять. +const bookingLessBalanceRuleScope = { + includedScopes: [], + excludedScopes: [ + { formType: "FORM_4", sheetName: "AHR" }, + { formType: "FORM_4", sheetName: "OPER" }, + ], +}; + export const validationRules = [ { id: "remaining-booking", @@ -72,6 +93,7 @@ export const validationRules = [ Number(value) !== 0, }, { + ...bookingLessBalanceRuleScope, id: "booking-less-balance", columnKeys: bookingLessBalance.flat(), errorColumnKeys: bookingLessBalance.map(([bookingKey]) => bookingKey), @@ -80,8 +102,41 @@ export const validationRules = [ const pair = bookingLessBalanceByColumn.get(columnKey); if (!pair) return false; - const booking = pair.bookingKey.split(".").reduce((value, key) => value?.[key], row); - const balance = pair.balanceKey.split(".").reduce((value, key) => value?.[key], row); + const booking = pair.bookingKey + .split(".") + .reduce((value, key) => value?.[key], row); + const balance = pair.balanceKey + .split(".") + .reduce((value, key) => value?.[key], row); + + return ( + booking !== null && + booking !== undefined && + booking !== "" && + balance !== null && + balance !== undefined && + balance !== "" && + Number.isFinite(Number(booking)) && + Number.isFinite(Number(balance)) && + Number(booking) > Number(balance) + ); + }, + }, + { + id: "booking-less-balance-4-form", + columnKeys: bookingLessBalance4From.flat(), + errorColumnKeys: bookingLessBalance4From.map(([bookingKey]) => bookingKey), + message: "Сумма бронирования превышает остаток после бронирования", + isInvalid: (_value, row, columnKey) => { + const pair = bookingLessBalance4FormByColumn.get(columnKey); + if (!pair) return false; + + const booking = pair.bookingKey + .split(".") + .reduce((value, key) => value?.[key], row); + const balance = pair.balanceKey + .split(".") + .reduce((value, key) => value?.[key], row); return ( booking !== null && @@ -105,8 +160,12 @@ export const validationRules = [ const pair = transferLessTotalByColumn.get(columnKey); if (!pair) return false; - const transfer = pair.transferKey.split(".").reduce((value, key) => value?.[key], row); - const total = pair.totalKey.split(".").reduce((value, key) => value?.[key], row); + const transfer = pair.transferKey + .split(".") + .reduce((value, key) => value?.[key], row); + const total = pair.totalKey + .split(".") + .reduce((value, key) => value?.[key], row); return ( transfer !== null && diff --git a/web/src/components/RealtimeTable/hooks/useValidationRules.js b/web/src/components/RealtimeTable/hooks/useValidationRules.js index 4007e47..c3be814 100644 --- a/web/src/components/RealtimeTable/hooks/useValidationRules.js +++ b/web/src/components/RealtimeTable/hooks/useValidationRules.js @@ -1,30 +1,34 @@ import { useCallback, useMemo } from 'react'; import { validationRules } from '../constants/validation'; -import { buildColumnHeaderPaths, collectInvalidErrors } from '../utils/validationUtils'; +import { buildColumnHeaderPaths, collectInvalidErrors, isValidationRuleEnabled } from '../utils/validationUtils'; -export const useValidationRules = (data = [], columns = []) => { +export const useValidationRules = (data = [], columns = [], formType, sheetName) => { const headerPaths = useMemo(() => buildColumnHeaderPaths(columns), [columns]); + const activeRules = useMemo( + () => validationRules.filter((rule) => isValidationRuleEnabled(rule, { formType, sheetName })), + [formType, sheetName], + ); const errors = useMemo(() => { const result = []; const seen = new Set(); - for (const rule of validationRules) { + for (const rule of activeRules) { collectInvalidErrors(data, rule, headerPaths, result, seen); } return result; - }, [data, headerPaths]); + }, [activeRules, data, headerPaths]); const isCellInvalid = useCallback( (columnId, value, row) => - validationRules.some( + activeRules.some( (rule) => rule.columnKeys.includes(columnId) && (!rule.appliesToRow || rule.appliesToRow(row)) && rule.isInvalid(value, row, columnId), ), - [], + [activeRules], ); return { errors, isCellInvalid }; diff --git a/web/src/components/RealtimeTable/utils/validationUtils.js b/web/src/components/RealtimeTable/utils/validationUtils.js index 3d12579..c17d470 100644 --- a/web/src/components/RealtimeTable/utils/validationUtils.js +++ b/web/src/components/RealtimeTable/utils/validationUtils.js @@ -1,6 +1,21 @@ // Достаёт вложенное поле из объекта строки таблицы export const getValueByPath = (row, path) => path.split('.').reduce((value, key) => value?.[key], row); +const matchesValidationScope = (scope, { formType, sheetName }) => + (!scope.formType || scope.formType === formType) && + (!scope.sheetName || scope.sheetName === sheetName); + +// Пустой includedScopes означает «работает везде»; исключения приоритетнее. +export const isValidationRuleEnabled = (rule, context = {}) => { + const includedScopes = rule.includedScopes || []; + const excludedScopes = rule.excludedScopes || []; + + if (excludedScopes.some((scope) => matchesValidationScope(scope, context))) return false; + if (includedScopes.length && !includedScopes.some((scope) => matchesValidationScope(scope, context))) return false; + + return true; +}; + // Строит пути заголовков колонок export const buildColumnHeaderPaths = (columns = [], path = [], map = {}) => { for (const column of columns) {