diff --git a/apps/web/Components/Calculation/Form/Insurance/FinGAPTable/index.jsx b/apps/web/Components/Calculation/Form/Insurance/FinGAPTable/index.jsx
index 5ce9cb8..3547673 100644
--- a/apps/web/Components/Calculation/Form/Insurance/FinGAPTable/index.jsx
+++ b/apps/web/Components/Calculation/Form/Insurance/FinGAPTable/index.jsx
@@ -14,10 +14,10 @@ const Grid = styled(Flex)`
const Validation = observer(() => {
const store = useStore();
- const messages = store.$tables.fingap.validation.getMessages();
+ const errors = store.$tables.fingap.validation.getErrors();
- if (messages?.length) {
- return ;
+ if (errors?.length) {
+ return ;
}
return null;
diff --git a/apps/web/Components/Calculation/Form/Insurance/InsuranceTable/index.jsx b/apps/web/Components/Calculation/Form/Insurance/InsuranceTable/index.jsx
index 6cc6fde..24ef8af 100644
--- a/apps/web/Components/Calculation/Form/Insurance/InsuranceTable/index.jsx
+++ b/apps/web/Components/Calculation/Form/Insurance/InsuranceTable/index.jsx
@@ -19,10 +19,10 @@ const TableWrapper = styled.div`
const Validation = observer(() => {
const store = useStore();
- const messages = store.$tables.insurance.validation.getMessages();
+ const errors = store.$tables.insurance.validation.getErrors();
- if (messages?.length) {
- return ;
+ if (errors?.length) {
+ return ;
}
return null;
diff --git a/apps/web/config/process/default.ts b/apps/web/config/process/default.ts
index f81c6b9..4cb4fe8 100644
--- a/apps/web/config/process/default.ts
+++ b/apps/web/config/process/default.ts
@@ -3,7 +3,7 @@ import * as bonuses from '@/process/bonuses';
// import * as calculate from '@/process/calculate';
import * as configurator from '@/process/configurator';
// import * as createKP from '@/process/create-kp';
-// import * as fingap from '@/process/fingap';
+import * as fingap from '@/process/fingap';
// import * as gibdd from '@/process/gibdd';
import { useProcess } from '@/process/hooks';
// import * as insurance from '@/process/insurance';
@@ -11,7 +11,7 @@ import { useProcess } from '@/process/hooks';
// import * as leasingObject from '@/process/leasing-object';
// import * as leasingWithoutKasko from '@/process/leasing-without-kasko';
// import * as loadKP from '@/process/load-kp';
-// import * as payments from '@/process/payments';
+import * as payments from '@/process/payments';
// import * as price from '@/process/price';
// import * as subsidy from '@/process/subsidy';
// import * as subsidyImportProgram from '@/process/subsidy-import-program';
@@ -24,7 +24,7 @@ export default function useReactions() {
// useProcess(calculate);
// useProcess(supplierAgent);
// useProcess(price);
- // useProcess(fingap);
+ useProcess(fingap);
// useProcess(leasingWithoutKasko);
// useProcess(subsidy);
// useProcess(leasingObject);
@@ -33,7 +33,7 @@ export default function useReactions() {
useProcess(bonuses);
// useProcess(usedPl);
// useProcess(subsidyImportProgram);
- // useProcess(payments);
+ useProcess(payments);
// useProcess(gibdd);
// useProcess(addProduct);
// useProcess(insurance);
diff --git a/apps/web/process/fingap/reactions/validation.ts b/apps/web/process/fingap/reactions/validation.ts
index 3f8df5f..580208e 100644
--- a/apps/web/process/fingap/reactions/validation.ts
+++ b/apps/web/process/fingap/reactions/validation.ts
@@ -1,9 +1,14 @@
import type { ProcessContext } from '@/process/types';
+import ValidationHelper from '@/stores/validation/helper';
import { reaction } from 'mobx';
+import { uid } from 'radash';
+
+const key = uid(7);
export default function reactions({ store }: ProcessContext) {
const { $tables } = store;
+ const helper = new ValidationHelper();
reaction(
() => {
const hasPaymentsErrors = $tables.payments.validation.hasErrors;
@@ -15,10 +20,15 @@ export default function reactions({ store }: ProcessContext) {
};
},
({ hasPaymentsErrors, finGAPInsuranceCompany }) => {
- $tables.fingap.validate({
- invalid: finGAPInsuranceCompany !== null && hasPaymentsErrors,
- message: 'Неверно заполнены платежи',
- });
+ if (finGAPInsuranceCompany !== null && hasPaymentsErrors) {
+ const removeError = $tables.fingap.setError({
+ key,
+ message: 'Неверно заполнены платежи',
+ });
+ helper.add(removeError);
+ } else {
+ helper.removeErrors();
+ }
if (hasPaymentsErrors) {
$tables.fingap.clear();
diff --git a/apps/web/process/payments/lib/validation.ts b/apps/web/process/payments/lib/validation.ts
deleted file mode 100644
index ae38795..0000000
--- a/apps/web/process/payments/lib/validation.ts
+++ /dev/null
@@ -1,138 +0,0 @@
-import { SEASONS_PERIOD_NUMBER, SEASONS_PERIODS } from './seasons-constants';
-import { MIN_PAYMENT } from '@/constants/values';
-import type RootStore from '@/stores/root';
-import { counting, max, min, shift, sort } from 'radash';
-import { areEqual, isSorted } from 'tools/array';
-
-// eslint-disable-next-line sonarjs/cognitive-complexity
-export default function validatePaymentsTable({ $calculation, $tables }: RootStore) {
- /**
- * в таблице платежей в столбце Соотношение платежей
- * для строк с 2 до "Срок лизинга-1" минимальное значение должно быть равно 3
- */
- {
- const leasingPeriod = $calculation.element('tbxLeasingPeriod').getValue();
- const targetPayments = $tables.payments.values.slice(1, leasingPeriod - 1);
-
- if (!targetPayments.every((payment) => payment >= MIN_PAYMENT)) {
- return `Минимальное значение платежа должно быть равно ${MIN_PAYMENT}`;
- }
- }
-
- switch ($calculation.element('radioGraphType').getValue()) {
- // Дегрессия
- case 100_000_001: {
- if (!$calculation.element('selectSeasonType').getValue()) {
- return 'Не выбран тип дегрессии';
- }
-
- /**
- * при Дегрессии все значения не должны быть равны друг другу
- * + что при Легком старте 2,3 и 4 платежи не должны быть равны 100
- */
- {
- const leasingPeriod = $calculation.element('tbxLeasingPeriod').getValue();
- const targetPayments = $tables.payments.values.slice(1, leasingPeriod - 1);
-
- if (new Set(targetPayments).size === 1) {
- return 'Платежи не должны быть равны друг другу';
- }
- }
-
- /**
- * Проверка на возрастание
- */
- {
- const leasingPeriod = $calculation.element('tbxLeasingPeriod').getValue();
- const targetPayments = $tables.payments.values.slice(1, leasingPeriod - 1);
-
- for (let i = 2; i < targetPayments.length - 1; i += 1) {
- if (targetPayments[i] > targetPayments[i - 1]) {
- return 'Платежи должны убывать';
- }
- }
- }
- /**
- * Если вид графика = Дегрессия И значения в "Соотношении платежей" у 2, 3 и 4 платежа отличаются друг от друга не более чем на 10
- * (т.е. берем значения в этих полях, определяем максимальное и минимальное значение и смотрим на их разницу)
- * то не осуществлять Расчет графика и выводить ошибку "Нельзя осуществить расчет - указана очень жетская дегрессия.
- * На 2-4 платежах Соотношение платежа должен отличаться не более чем на 10%",
- * иначе осуществлять расчет
- */
- {
- const targetPayments = $tables.payments.values.slice(1, 4);
-
- if ((max(targetPayments) || 0) - (min(targetPayments) || 0) > 10) {
- return 'Указана очень жесткая дегрессия. На 2-4 платежах Соотношение платежа должен отличаться не более чем на 10%';
- }
- }
-
- /**
- * Если вид графика = Дегрессия И значения в "Соотношении платежей" для строк с 2 До "Срок лизинга-1" как минимум 2 раза по 2 платежа должны между собой быть равны
- * (т.е. берем значения "Соотношения платежей" для строк с 2 до "Срок лизинга-1" и делаем сводную таблицу - если кол-во одинаковых значение больше 2 встречаются 2 и более раза),
- * то осуществлять расчет,
- * иначе не осуществлять Расчет графика и выводить ошибку "Нельзя осуществить расчет - указана очень жетская дегрессия. Не менее чем у 4х платежей "Соотношение платежа" должно не отличаться между самой",
- */
- {
- const leasingPeriod = $calculation.element('tbxLeasingPeriod').getValue();
- const targetPayments = $tables.payments.values.slice(1, leasingPeriod - 1);
- const counts = counting(targetPayments, (v) => v);
- if (Object.values(counts).filter((count) => count > 1).length < 2) {
- return 'Указана очень жесткая дегрессия. Не менее чем у 4х платежей соотношение должно не отличаться между собой';
- }
- }
-
- break;
- }
-
- case 100_000_003: {
- const leasingPeriod = $calculation.element('tbxLeasingPeriod').getValue();
- if (leasingPeriod < 14) {
- return 'При сезонном виде графика срок лизинга должен быть больше 14 месяцев';
- }
-
- const seasonType = $calculation.element('selectSeasonType').getValue();
- if (!seasonType) {
- return 'Не выбран тип сезонности';
- }
-
- const highSeasonStartOption = $calculation.element('selectHighSeasonStart').getOption();
- if (!highSeasonStartOption) {
- return 'Не выбрано смещение сезонности';
- }
-
- {
- const seasons = $tables.payments.values.slice(1, SEASONS_PERIOD_NUMBER + 1);
- const shiftNumber = Number.parseInt(highSeasonStartOption.label, 10) - 2;
- const unshiftedSeasons = shift(seasons, -shiftNumber);
-
- const positions = SEASONS_PERIODS[seasonType];
- const seasonsValues = positions.map((position) => unshiftedSeasons[position]);
-
- if (isSorted(seasonsValues)) {
- return 'Сезонные платежи должны убывать';
- }
- }
-
- break;
- }
-
- // Легкий старт
- case 100_000_004: {
- const targetPayments = $tables.payments.values.slice(1, 4);
- const sortedPayments = sort(targetPayments, (x) => x);
- const areEqualPayments = new Set(targetPayments).size === 1;
-
- if (!areEqual(targetPayments, sortedPayments) || areEqualPayments) {
- return '2, 3, 4 платежи должны возрастать';
- }
-
- break;
- }
- default: {
- return null;
- }
- }
-
- return null;
-}
diff --git a/apps/web/process/payments/reactions/validation.ts b/apps/web/process/payments/reactions/validation.ts
index 7d6eefb..9a3fdc8 100644
--- a/apps/web/process/payments/reactions/validation.ts
+++ b/apps/web/process/payments/reactions/validation.ts
@@ -1,40 +1,50 @@
-import validatePaymentsTable from '../lib/validation';
-import { MIN_LASTPAYMENT_NSIB } from '@/constants/values';
+import { createValidationSchema } from '../validation';
+import type { Elements } from '@/Components/Calculation/config/map/values';
import type { ProcessContext } from '@/process/types';
import ValidationHelper from '@/stores/validation/helper';
import { comparer, reaction, toJS } from 'mobx';
+import { uid } from 'radash';
-export default function reactions({ store }: ProcessContext) {
- const { $calculation, $tables } = store;
+const key = uid(7);
- const validationHelper = new ValidationHelper();
+export default function reactions(context: ProcessContext) {
+ const { $calculation, $tables } = context.store;
+
+ const validationSchema = createValidationSchema();
+ const helper = new ValidationHelper();
reaction(
() => {
const payments = toJS($tables.payments.values);
- const graphType = $calculation.element('radioGraphType').getValue();
- const seasonType = $calculation.element('selectSeasonType').getValue();
- const highSeasonStart = $calculation.element('selectHighSeasonStart').getValue();
- const leasingPeriod = $calculation.element('tbxLeasingPeriod').getValue();
+ const values = $calculation.$values.getValues([
+ 'graphType',
+ 'highSeasonStart',
+ 'leasingPeriod',
+ 'seasonType',
+ 'insNSIB',
+ 'lastPaymentRub',
+ ]);
return {
- graphType,
- highSeasonStart,
- leasingPeriod,
- payments,
- seasonType,
+ payments: { values: payments },
+ ...values,
};
},
- () => {
- validationHelper.removeErrors();
+ async (values) => {
+ helper.removeErrors();
+ const validationResult = await validationSchema.safeParseAsync(values);
- const errorText = validatePaymentsTable(store);
-
- if (errorText) {
- $tables.payments.validate({
- helper: validationHelper,
- invalid: errorText !== null,
- message: errorText,
+ if (!validationResult.success) {
+ validationResult.error.errors.forEach(({ path, message }) => {
+ (path as Array).forEach((elementName) => {
+ if (elementName === 'payments') {
+ const removeError = $tables.payments.setError({ key, message });
+ if (removeError) helper.add(removeError);
+ } else {
+ const removeError = $calculation.element(elementName).setError({ key, message });
+ if (removeError) helper.add(removeError);
+ }
+ });
});
}
},
@@ -43,22 +53,4 @@ export default function reactions({ store }: ProcessContext) {
equals: comparer.structural,
}
);
-
- reaction(
- () => {
- const lastPaymentRub = $calculation.element('tbxLastPaymentRub').getValue();
- const insNSIB = $calculation.element('selectInsNSIB').getValue();
-
- return {
- insNSIB,
- lastPaymentRub,
- };
- },
- ({ lastPaymentRub, insNSIB }) => {
- $calculation.element('tbxLastPaymentRub').validate({
- invalid: Boolean(insNSIB) && lastPaymentRub < MIN_LASTPAYMENT_NSIB,
- message: `Последний платеж меньше ${MIN_LASTPAYMENT_NSIB} руб. не может быть при наличии НСИБ, укажите большее значение`,
- });
- }
- );
}
diff --git a/apps/web/process/payments/validation.ts b/apps/web/process/payments/validation.ts
new file mode 100644
index 0000000..5a5fc1f
--- /dev/null
+++ b/apps/web/process/payments/validation.ts
@@ -0,0 +1,215 @@
+/* eslint-disable sonarjs/cognitive-complexity */
+/* eslint-disable complexity */
+import { SEASONS_PERIOD_NUMBER, SEASONS_PERIODS } from './lib/seasons-constants';
+import { selectHighSeasonStart } from '@/config/default-options';
+import PaymentsSchema from '@/config/schema/payments';
+import ValuesSchema from '@/config/schema/values';
+import { MIN_LASTPAYMENT_NSIB, MIN_PAYMENT } from '@/constants/values';
+import { counting, max, min, shift, sort } from 'radash';
+import { areEqual, isSorted } from 'tools/array';
+import { z } from 'zod';
+
+export function createValidationSchema() {
+ return ValuesSchema.pick({
+ graphType: true,
+ highSeasonStart: true,
+ insNSIB: true,
+ lastPaymentRub: true,
+ leasingPeriod: true,
+ seasonType: true,
+ })
+ .extend({
+ payments: PaymentsSchema,
+ })
+ .superRefine(
+ async (
+ {
+ graphType,
+ highSeasonStart,
+ leasingPeriod,
+ payments,
+ seasonType,
+ insNSIB,
+ lastPaymentRub,
+ },
+ ctx
+ ) => {
+ if (Boolean(insNSIB) && lastPaymentRub < MIN_LASTPAYMENT_NSIB) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: `Последний платеж не может быть меньше ${MIN_LASTPAYMENT_NSIB} руб. при наличии НСИБ, укажите большее значение`,
+ path: ['tbxLastPaymentRub'],
+ });
+ }
+
+ {
+ const targetPayments = payments.values.slice(1, leasingPeriod - 1);
+
+ if (!targetPayments.every((payment) => payment >= MIN_PAYMENT)) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: `Минимальное значение платежа должно быть равно ${MIN_PAYMENT}`,
+ path: ['payments'],
+ });
+ }
+ }
+
+ switch (graphType) {
+ // Дегрессия
+ case 100_000_001: {
+ if (!seasonType) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: 'Не выбран тип дегрессии',
+ path: ['payments', 'selectSeasonType'],
+ });
+ break;
+ }
+
+ /**
+ * при Дегрессии все значения не должны быть равны друг другу
+ * + что при Легком старте 2,3 и 4 платежи не должны быть равны 100
+ */
+ {
+ const targetPayments = payments.values.slice(1, leasingPeriod - 1);
+ if (new Set(targetPayments).size === 1) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: 'Платежи не должны быть равны друг другу',
+ path: ['payments'],
+ });
+ }
+ }
+
+ /**
+ * Проверка на возрастание
+ */
+ {
+ const targetPayments = payments.values.slice(1, leasingPeriod - 1);
+ for (let i = 2; i < targetPayments.length - 1; i += 1) {
+ if (targetPayments[i] > targetPayments[i - 1]) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: 'Платежи должны убывать',
+ path: ['payments'],
+ });
+ }
+ }
+ }
+
+ /**
+ * Если вид графика = Дегрессия И значения в "Соотношении платежей" у 2, 3 и 4 платежа отличаются друг от друга не более чем на 10
+ * (т.е. берем значения в этих полях, определяем максимальное и минимальное значение и смотрим на их разницу)
+ * то не осуществлять Расчет графика и выводить ошибку "Нельзя осуществить расчет - указана очень жетская дегрессия.
+ * На 2-4 платежах Соотношение платежа должен отличаться не более чем на 10%",
+ * иначе осуществлять расчет
+ */
+ {
+ const targetPayments = payments.values.slice(1, 4);
+
+ if ((max(targetPayments) || 0) - (min(targetPayments) || 0) > 10) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message:
+ 'Указана очень жесткая дегрессия. На 2-4 платежах Соотношение платежа должен отличаться не более чем на 10%',
+ path: ['payments'],
+ });
+ }
+ }
+
+ /**
+ * Если вид графика = Дегрессия И значения в "Соотношении платежей" для строк с 2 До "Срок лизинга-1" как минимум 2 раза по 2 платежа должны между собой быть равны
+ * (т.е. берем значения "Соотношения платежей" для строк с 2 до "Срок лизинга-1" и делаем сводную таблицу - если кол-во одинаковых значение больше 2 встречаются 2 и более раза),
+ * то осуществлять расчет,
+ * иначе не осуществлять Расчет графика и выводить ошибку "Нельзя осуществить расчет - указана очень жетская дегрессия. Не менее чем у 4х платежей "Соотношение платежа" должно не отличаться между самой",
+ */
+ {
+ const targetPayments = payments.values.slice(1, leasingPeriod - 1);
+ const counts = counting(targetPayments, (v) => v);
+ if (Object.values(counts).filter((count) => count > 1).length < 2) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message:
+ 'Указана очень жесткая дегрессия. Не менее чем у 4х платежей соотношение должно не отличаться между собой',
+ path: ['payments'],
+ });
+ }
+ }
+
+ break;
+ }
+
+ case 100_000_003: {
+ if (leasingPeriod < 14) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: 'При сезонном виде графика срок лизинга должен быть больше 14 месяцев',
+ path: ['payments', 'tbxLeasingPeriod'],
+ });
+ }
+
+ if (!seasonType) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: 'Не выбран тип сезонности',
+ path: ['payments', 'selectSeasonType'],
+ });
+ break;
+ }
+
+ if (!highSeasonStart) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: 'Не выбрано смещение сезонности',
+ path: ['payments', 'selectHighSeasonStart'],
+ });
+ break;
+ }
+
+ const seasons = payments.values.slice(1, SEASONS_PERIOD_NUMBER + 1);
+ const highSeasonStartOption = selectHighSeasonStart.find(
+ (x) => x.value === highSeasonStart
+ );
+
+ if (highSeasonStartOption) {
+ const shiftNumber = Number.parseInt(highSeasonStartOption.label, 10) - 2;
+ const unshiftedSeasons = shift(seasons, -shiftNumber);
+
+ const positions = SEASONS_PERIODS[seasonType];
+ const seasonsValues = positions.map((position) => unshiftedSeasons[position]);
+
+ if (isSorted(seasonsValues)) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: 'Сезонные платежи должны убывать',
+ path: ['payments'],
+ });
+ }
+ }
+
+ break;
+ }
+
+ // Легкий старт
+ case 100_000_004: {
+ const targetPayments = payments.values.slice(1, 4);
+ const sortedPayments = sort(targetPayments, (x) => x);
+ const areEqualPayments = new Set(targetPayments).size === 1;
+
+ if (!areEqual(targetPayments, sortedPayments) || areEqualPayments) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: '2, 3, 4 платежи должны возрастать',
+ path: ['payments'],
+ });
+ }
+
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+ }
+ );
+}
diff --git a/apps/web/stores/tables/fingap/index.ts b/apps/web/stores/tables/fingap/index.ts
index 9df4da9..82d5f6d 100644
--- a/apps/web/stores/tables/fingap/index.ts
+++ b/apps/web/stores/tables/fingap/index.ts
@@ -1,5 +1,5 @@
import Validation from '../../validation';
-import type { RemoveError, ValidationParams } from '../../validation/types';
+import type { ValidationParams } from '../../validation/types';
import type * as FinGAP from '@/Components/Calculation/Form/Insurance/FinGAPTable/types';
import type RootStore from '@/stores/root';
import type { IObservableArray } from 'mobx';
@@ -38,17 +38,10 @@ export default class FinGAPTable {
.reduce((sum, risk) => sum + risk.premium, 0);
}
- public validate = ({ invalid, message, helper }: ValidationParams) => {
- let removeError: RemoveError | undefined;
+ public setError = (params: ValidationParams) => this.validation.setError(params);
- if (invalid) {
- removeError = this.validation?.addError(message);
- if (helper && removeError) helper.add(removeError);
- } else {
- this.validation?.removeError(message);
- }
-
- return removeError;
+ public removeError = (params: Pick) => {
+ this.validation.removeError(params);
};
public clear = () => {
diff --git a/apps/web/stores/tables/payments/index.ts b/apps/web/stores/tables/payments/index.ts
index cbe63eb..ce9b135 100644
--- a/apps/web/stores/tables/payments/index.ts
+++ b/apps/web/stores/tables/payments/index.ts
@@ -1,5 +1,5 @@
import Validation from '../../validation';
-import type { RemoveError, ValidationParams } from '../../validation/types';
+import type { ValidationParams } from '../../validation/types';
import type { Row } from './types';
import type RootStore from '@/stores/root';
import type { IObservableArray } from 'mobx';
@@ -72,17 +72,10 @@ export default class PaymentsTable {
this.setStatuses(statuses);
};
- public validate = ({ invalid, message, helper }: ValidationParams) => {
- let removeError: RemoveError | undefined;
+ public setError = (params: ValidationParams) => this.validation.setError(params);
- if (invalid) {
- removeError = this.validation?.addError(message);
- if (helper && removeError) helper.add(removeError);
- } else {
- this.validation?.removeError(message);
- }
-
- return removeError;
+ public removeError = (params: Pick) => {
+ this.validation.removeError(params);
};
public reset = () => {