process/payments: use new validation

This commit is contained in:
vchikalkin 2023-03-07 12:07:24 +03:00
parent 14c16ec1c4
commit 6d375dc638
9 changed files with 280 additions and 215 deletions

View File

@ -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 <Alert type="error" banner message={messages[0]} />;
if (errors?.length) {
return <Alert type="error" banner message={errors[0].message} />;
}
return null;

View File

@ -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 <Alert type="error" banner message={messages[0]} />;
if (errors?.length) {
return <Alert type="error" banner message={errors[0].message} />;
}
return null;

View File

@ -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);

View File

@ -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();

View File

@ -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;
}

View File

@ -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<Elements & 'payments'>).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} руб. не может быть при наличии НСИБ, укажите большее значение`,
});
}
);
}

View File

@ -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;
}
}
}
);
}

View File

@ -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<ValidationParams, 'key'>) => {
this.validation.removeError(params);
};
public clear = () => {

View File

@ -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<ValidationParams, 'key'>) => {
this.validation.removeError(params);
};
public reset = () => {