исключила 1-3 кв для проверки

This commit is contained in:
PotapovaA 2026-08-13 17:43:14 +03:00
parent e11661a26b
commit df6ea39194
3 changed files with 89 additions and 11 deletions

View File

@ -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 = [ const transferLessTotal = [
["data.q1.transfer_econ", "data.q1.total"], ["data.q1.transfer_econ", "data.q1.total"],
["data.q2.transfer_econ", "data.q2.total"], ["data.q2.transfer_econ", "data.q2.total"],
["data.q3.transfer_econ", "data.q3.total"], ["data.q3.transfer_econ", "data.q3.total"],
["data.q4.transfer_econ", "data.q4.total"], ["data.q4.transfer_econ", "data.q4.total"],
] ];
const transferLessTotalByColumn = new Map( const transferLessTotalByColumn = new Map(
transferLessTotal.flatMap(([transferKey, totalKey]) => [ transferLessTotal.flatMap(([transferKey, totalKey]) => [
[transferKey, { transferKey: transferKey, totalKey: 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 = [ export const validationRules = [
{ {
id: "remaining-booking", id: "remaining-booking",
@ -72,6 +93,7 @@ export const validationRules = [
Number(value) !== 0, Number(value) !== 0,
}, },
{ {
...bookingLessBalanceRuleScope,
id: "booking-less-balance", id: "booking-less-balance",
columnKeys: bookingLessBalance.flat(), columnKeys: bookingLessBalance.flat(),
errorColumnKeys: bookingLessBalance.map(([bookingKey]) => bookingKey), errorColumnKeys: bookingLessBalance.map(([bookingKey]) => bookingKey),
@ -80,8 +102,41 @@ export const validationRules = [
const pair = bookingLessBalanceByColumn.get(columnKey); const pair = bookingLessBalanceByColumn.get(columnKey);
if (!pair) return false; if (!pair) return false;
const booking = pair.bookingKey.split(".").reduce((value, key) => value?.[key], row); const booking = pair.bookingKey
const balance = pair.balanceKey.split(".").reduce((value, key) => value?.[key], row); .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 ( return (
booking !== null && booking !== null &&
@ -105,8 +160,12 @@ export const validationRules = [
const pair = transferLessTotalByColumn.get(columnKey); const pair = transferLessTotalByColumn.get(columnKey);
if (!pair) return false; if (!pair) return false;
const transfer = pair.transferKey.split(".").reduce((value, key) => value?.[key], row); const transfer = pair.transferKey
const total = pair.totalKey.split(".").reduce((value, key) => value?.[key], row); .split(".")
.reduce((value, key) => value?.[key], row);
const total = pair.totalKey
.split(".")
.reduce((value, key) => value?.[key], row);
return ( return (
transfer !== null && transfer !== null &&

View File

@ -1,30 +1,34 @@
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { validationRules } from '../constants/validation'; 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 headerPaths = useMemo(() => buildColumnHeaderPaths(columns), [columns]);
const activeRules = useMemo(
() => validationRules.filter((rule) => isValidationRuleEnabled(rule, { formType, sheetName })),
[formType, sheetName],
);
const errors = useMemo(() => { const errors = useMemo(() => {
const result = []; const result = [];
const seen = new Set(); const seen = new Set();
for (const rule of validationRules) { for (const rule of activeRules) {
collectInvalidErrors(data, rule, headerPaths, result, seen); collectInvalidErrors(data, rule, headerPaths, result, seen);
} }
return result; return result;
}, [data, headerPaths]); }, [activeRules, data, headerPaths]);
const isCellInvalid = useCallback( const isCellInvalid = useCallback(
(columnId, value, row) => (columnId, value, row) =>
validationRules.some( activeRules.some(
(rule) => (rule) =>
rule.columnKeys.includes(columnId) && rule.columnKeys.includes(columnId) &&
(!rule.appliesToRow || rule.appliesToRow(row)) && (!rule.appliesToRow || rule.appliesToRow(row)) &&
rule.isInvalid(value, row, columnId), rule.isInvalid(value, row, columnId),
), ),
[], [activeRules],
); );
return { errors, isCellInvalid }; return { errors, isCellInvalid };

View File

@ -1,6 +1,21 @@
// Достаёт вложенное поле из объекта строки таблицы // Достаёт вложенное поле из объекта строки таблицы
export const getValueByPath = (row, path) => path.split('.').reduce((value, key) => value?.[key], row); 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 = {}) => { export const buildColumnHeaderPaths = (columns = [], path = [], map = {}) => {
for (const column of columns) { for (const column of columns) {