merge experimental/zod-validation

This commit is contained in:
vchikalkin 2023-03-10 09:37:37 +03:00
parent 5748bb9ffd
commit 7fe8de83c8
43 changed files with 1613 additions and 1100 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

@ -33,10 +33,10 @@ const Validation = observer(() => {
const store = useStore();
const { payments } = store.$tables;
const messages = payments.validation.getMessages();
const errors = payments.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

@ -25,12 +25,12 @@ const AlertWrapper = styled(Box)`
function getElementsErrors($calculation) {
return Object.values($calculation.$validation).map((validation) => {
const elementErrors = validation.getMessages();
const elementErrors = validation.getErrors();
const elementTitle = validation.params.err_title;
return elementErrors.map((error) => (
return elementErrors.map(({ key, message }) => (
<AlertWrapper>
<Alert type="error" showIcon message={Message(elementTitle, error)} />
<Alert key={key} type="error" showIcon message={Message(elementTitle, message)} />
</AlertWrapper>
));
});
@ -38,18 +38,22 @@ function getElementsErrors($calculation) {
function getPaymentsTableErrors($tables) {
const { payments } = $tables;
const messages = payments.validation.getMessages();
const errors = payments.validation.getErrors();
const title = payments.validation.params.err_title;
return messages.map((text) => <Alert type="error" showIcon message={Message(title, text)} />);
return errors.map(({ key, message }) => (
<Alert key={key} type="error" showIcon message={Message(title, message)} />
));
}
function getInsuranceTableErrors($tables) {
const { insurance } = $tables;
const messages = insurance.validation.getMessages();
const errors = insurance.validation.getErrors();
const title = insurance.validation.params.err_title;
return messages.map((text) => <Alert type="error" showIcon message={Message(title, text)} />);
return errors.map(({ key, message }) => (
<Alert key={key} type="error" showIcon message={Message(title, message)} />
));
}
const Errors = observer(() => {

View File

@ -7,3 +7,4 @@ export const MAX_LEASING_PERIOD = 60;
export const MIN_LASTPAYMENT_NSIB = 3500;
export const MIN_PAYMENT = 3;
export const VAT = 0.2;
export const MAX_MASS = 3500;

View File

@ -1,10 +1,10 @@
/* eslint-disable @typescript-eslint/naming-convention */
import type { ProcessContext } from '../types';
import { normalizeOptions } from '@/../../packages/tools';
import * as CRMTypes from '@/graphql/crm.types';
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import { reaction } from 'mobx';
import { normalizeOptions } from 'tools';
dayjs.extend(utc);

View File

@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/naming-convention */
import type { ProcessContext } from '../../../types';
import type { ValidationContext } from '../../types';
import { getUser } from '@/api/user/query';
import type { ElementsTypes } from '@/Components/Calculation/config/map/values';
import { STALE_TIME } from '@/constants/request';
@ -8,7 +8,7 @@ import dayjs from 'dayjs';
export type ProductId = ElementsTypes['selectProduct'];
export default function helper({ apolloClient, queryClient }: ProcessContext) {
export default function helper({ apolloClient, queryClient }: ValidationContext) {
return {
async getCoefficient(productId: ProductId) {
if (!productId) {

View File

@ -1,11 +1,11 @@
/* eslint-disable @typescript-eslint/naming-convention */
import type { ProcessContext } from '../../types';
import helper from './lib/helper';
import { makeDisposable } from '@/../../packages/tools';
import helper from '../lib/helper';
import * as CRMTypes from '@/graphql/crm.types';
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import { reaction } from 'mobx';
import { makeDisposable } from 'tools';
dayjs.extend(utc);

View File

@ -1,23 +1,35 @@
import helper from './lib/helper';
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 { reaction } from 'mobx';
import { round } from 'tools';
import { uid } from 'radash';
const key = uid(7);
export default function reactions(context: ProcessContext) {
const { store } = context;
const { $calculation } = store;
const { getCoefficient } = helper(context);
const validationSchema = createValidationSchema(context);
const helper = new ValidationHelper();
reaction(
() => $calculation.$values.getValues(['product', 'saleBonus']),
async ({ product: productId, saleBonus }) => {
const coefficient = await getCoefficient(productId);
const maxBonus = (coefficient?.evo_sot_coefficient || 0) * 100;
async (values) => {
helper.removeErrors();
const validationResult = await validationSchema.safeParseAsync(values);
$calculation.element('tbxSaleBonus').validate({
invalid: round(saleBonus, 2) > round(maxBonus, 2),
message: 'Размер бонуса МПЛ не может быть выше установленного по СОТ',
});
if (!validationResult.success) {
validationResult.error.errors.forEach(({ path, message }) => {
(path as Elements[]).forEach((elementName) => {
const removeError = $calculation.element(elementName).setError({ key, message });
if (removeError) helper.add(removeError);
});
});
}
},
{
delay: 100,
}
);
}

View File

@ -0,0 +1,24 @@
import type { ValidationContext } from '../types';
import helper from './lib/helper';
import ValuesSchema from '@/config/schema/values';
import { round } from 'tools';
import { z } from 'zod';
export function createValidationSchema(context: ValidationContext) {
const { getCoefficient } = helper(context);
return ValuesSchema.pick({ product: true, saleBonus: true }).superRefine(
async ({ product, saleBonus }, ctx) => {
const coefficient = await getCoefficient(product);
const maxBonus = (coefficient?.evo_sot_coefficient || 0) * 100;
if (round(saleBonus, 2) > round(maxBonus, 2)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Размер бонуса МПЛ не может быть выше установленного по СОТ',
path: ['tbxSaleBonus'],
});
}
}
);
}

View File

@ -2,7 +2,9 @@ import types from '@/Components/Calculation/config/elements-types';
import type * as Values from '@/Components/Calculation/config/map/values';
import type * as Insurance from '@/Components/Calculation/Form/Insurance/InsuranceTable/types';
import type { ProcessContext } from '@/process/types';
import ValidationHelper from '@/stores/validation/helper';
import { reaction } from 'mobx';
import { uid } from 'radash';
import type { BaseOption } from 'ui/elements/types';
function hasInvalidValueOrOptions(value: unknown, options: Array<BaseOption<unknown>>) {
@ -21,6 +23,7 @@ export default function reactions({ store }: ProcessContext) {
const hasElementsErrors = Object.values($calculation.$validation).some(
(validation) => validation.hasErrors
);
const hasPaymentsErrors = $tables.payments.validation.hasErrors;
const hasInsuranceErrors = $tables.insurance.validation.hasErrors;
const hasFingapErrors = $tables.fingap.validation.hasErrors;
@ -44,6 +47,10 @@ export default function reactions({ store }: ProcessContext) {
/**
* Проверяем, что выбранное значение элемента есть в списке
*/
const key = uid(7);
const message = 'Выбранное значение отсутствует в списке';
function validateOptionsElement(elementName: Values.Elements) {
const type = types[elementName];
if (type().typeName !== 'Options') {
@ -52,6 +59,8 @@ export default function reactions({ store }: ProcessContext) {
const element = $calculation.element(elementName);
const validationHelper = new ValidationHelper();
reaction(
() => {
const options = element.getOptions();
@ -63,11 +72,16 @@ export default function reactions({ store }: ProcessContext) {
};
},
({ value, options }) => {
element.validate({
invalid: hasInvalidValueOrOptions(value, options),
message: 'Выбранное значение отсутствует в списке',
silent: true,
});
if (hasInvalidValueOrOptions(value, options)) {
const removeError = element.setError({
key,
message,
silent: true,
});
if (removeError) validationHelper.add(removeError);
} else {
validationHelper.removeErrors();
}
},
{
delay: 100,
@ -95,6 +109,8 @@ export default function reactions({ store }: ProcessContext) {
function validateInsuranceCompany(rowKey: Insurance.Keys) {
const row = $tables.insurance.row(rowKey);
const validationHelper = new ValidationHelper();
reaction(
() => {
const options = row.getOptions('insuranceCompany');
@ -106,11 +122,16 @@ export default function reactions({ store }: ProcessContext) {
};
},
({ value, options }) => {
$tables.insurance.validate({
invalid: hasInvalidValueOrOptions(value, options),
message: 'Выбранное значение отсутствует в списке',
silent: true,
});
if (hasInvalidValueOrOptions(value, options)) {
const removeError = $tables.insurance.setError({
key,
message,
silent: true,
});
validationHelper.add(removeError);
} else {
validationHelper.removeErrors();
}
},
{
delay: 100,

View File

@ -1,44 +1,35 @@
/* eslint-disable @typescript-eslint/naming-convention */
import * as CRMTypes from '@/graphql/crm.types';
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 { reaction } from 'mobx';
import { uid } from 'radash';
export default function reactions({ store, apolloClient }: ProcessContext) {
const key = uid(7);
export default function reactions(context: ProcessContext) {
const { store } = context;
const { $calculation } = store;
/**
* На изменение поля Процет убывания платежей tbxParmentsDecreasePercent необходимо заложить проверку:
* Если значение поля меньше значения в поле "Минимальный % убывания платежей" evo_min_decreasing_perc из записи,
* указанной в поле ТарифselectTarif , то поле Процет убывания платежей tbxParmentsDecreasePercent должно обводиться красной рамкой
* и выводиться сообщение "Процент убывания не может быть меньше минимального значения по данному тарифу
* - <указывается значение из поля "Минимальный % убывания платежей">, иначе красная рамка снимается.
* При красной рамке в данном поле нельзя осуществить расчет графика.
*/
const validationSchema = createValidationSchema(context);
const helper = new ValidationHelper();
reaction(
() => ({
parmentsDecreasePercent: $calculation.element('tbxParmentsDecreasePercent').getValue(),
tarifId: $calculation.element('selectTarif').getValue(),
}),
async ({ parmentsDecreasePercent, tarifId }) => {
let evo_tarif: CRMTypes.GetTarifQuery['evo_tarif'] = null;
() => $calculation.$values.getValues(['parmentsDecreasePercent', 'tarif']),
async (values) => {
helper.removeErrors();
const validationResult = await validationSchema.safeParseAsync(values);
if (tarifId) {
const { data } = await apolloClient.query({
query: CRMTypes.GetTarifDocument,
variables: {
tarifId,
},
if (!validationResult.success) {
validationResult.error.errors.forEach(({ path, message }) => {
(path as Elements[]).forEach((elementName) => {
const removeError = $calculation.element(elementName).setError({ key, message });
if (removeError) helper.add(removeError);
});
});
({ evo_tarif } = data);
}
$calculation.element('tbxParmentsDecreasePercent').validate({
invalid: Boolean(
evo_tarif?.evo_min_decreasing_perc &&
parmentsDecreasePercent < evo_tarif?.evo_min_decreasing_perc
),
message: `Процент убывания не может быть меньше минимального значения по данному тарифу - ${evo_tarif?.evo_min_decreasing_perc}`,
});
},
{
delay: 100,
}
);
}

View File

@ -0,0 +1,41 @@
/* eslint-disable @typescript-eslint/naming-convention */
import type { ValidationContext } from '../types';
import ValuesSchema from '@/config/schema/values';
import * as CRMTypes from '@/graphql/crm.types';
import { z } from 'zod';
export function createValidationSchema({ apolloClient }: ValidationContext) {
return ValuesSchema.pick({ parmentsDecreasePercent: true, tarif: true }).superRefine(
async ({ parmentsDecreasePercent, tarif: tarifId }, ctx) => {
/**
* На изменение поля Процет убывания платежей tbxParmentsDecreasePercent необходимо заложить проверку:
* Если значение поля меньше значения в поле "Минимальный % убывания платежей" evo_min_decreasing_perc из записи,
* указанной в поле ТарифselectTarif , то поле Процет убывания платежей tbxParmentsDecreasePercent должно обводиться красной рамкой
* и выводиться сообщение "Процент убывания не может быть меньше минимального значения по данному тарифу
* - <указывается значение из поля "Минимальный % убывания платежей">, иначе красная рамка снимается.
* При красной рамке в данном поле нельзя осуществить расчет графика.
*/
if (tarifId) {
const {
data: { evo_tarif },
} = await apolloClient.query({
query: CRMTypes.GetTarifDocument,
variables: {
tarifId,
},
});
if (
evo_tarif?.evo_min_decreasing_perc &&
parmentsDecreasePercent < evo_tarif?.evo_min_decreasing_perc
) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `Процент убывания не может быть меньше минимального значения по данному тарифу - ${evo_tarif?.evo_min_decreasing_perc}`,
path: ['tbxParmentsDecreasePercent'],
});
}
}
}
);
}

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,7 +1,9 @@
/* eslint-disable @typescript-eslint/naming-convention */
import type { ProcessContext } from '../types';
import helper from './lib/helper';
import { createValidationSchema } from './validation';
import { getTransTax } from '@/api/1c/query';
import type { Elements } from '@/Components/Calculation/config/map/values';
import { selectObjectCategoryTax } from '@/config/default-options';
import { STALE_TIME } from '@/constants/request';
import * as CRMTypes from '@/graphql/crm.types';
@ -10,6 +12,7 @@ import type { QueryFunctionContext } from '@tanstack/react-query';
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import { reaction } from 'mobx';
import { uid } from 'radash';
import { makeDisposable, normalizeOptions } from 'tools';
dayjs.extend(utc);
@ -43,36 +46,6 @@ export function common({ store, apolloClient, queryClient }: ProcessContext) {
}
);
reaction(
() => $calculation.$values.getValues(['objectRegistration', 'vehicleTaxInYear']),
({ objectRegistration, vehicleTaxInYear }) => {
if (objectRegistration === 100_000_001) {
$calculation.element('tbxVehicleTaxInYear').unblock();
} else {
$calculation.element('tbxVehicleTaxInYear').resetValue().block();
}
$calculation.element('tbxVehicleTaxInYear').validate({
invalid: objectRegistration === 100_000_001 && !(vehicleTaxInYear > 0),
message: 'Значение должно быть больше 0',
});
}
);
reaction(
() => $calculation.$values.getValues(['objectRegistration', 'typePTS']),
({ objectRegistration, typePTS }) => {
if (objectRegistration === 100_000_001) {
$calculation.element('radioTypePTS').unblock();
} else {
$calculation.element('radioTypePTS').resetValue().block();
}
$calculation.element('radioTypePTS').validate({
invalid: objectRegistration === 100_000_001 && !typePTS,
message: 'Не заполнено поле',
});
}
);
makeDisposable(
() =>
reaction(
@ -503,89 +476,41 @@ export function common({ store, apolloClient, queryClient }: ProcessContext) {
);
}
export function validation({ store, apolloClient }: ProcessContext) {
const { $calculation } = store;
const key = uid(7);
export function validation(context: ProcessContext) {
const { store } = context;
const { $calculation } = store;
const validationSchema = createValidationSchema(context);
const validationHelper = new ValidationHelper();
reaction(
() => $calculation.$values.getValues(['leaseObjectCategory', 'maxMass']),
({ leaseObjectCategory, maxMass }) => {
$calculation.element('tbxMaxMass').validate({
invalid: leaseObjectCategory === 100_000_001 && maxMass > 3500,
message: 'При категории ТС = В Разрешенная макс.масса не может быть больше 3500 кг',
});
$calculation.element('tbxMaxMass').validate({
invalid: leaseObjectCategory === 100_000_002 && maxMass <= 3500,
message: 'При категории ТС = С Разрешенная макс.масса не может быть меньше 3500 кг',
});
() =>
$calculation.$values.getValues([
'leaseObjectCategory',
'maxMass',
'leaseObjectType',
'typePTS',
'objectRegistration',
'objectCategoryTax',
'insNSIB',
'vehicleTaxInYear',
]),
async (values) => {
validationHelper.removeErrors();
const validationResult = await validationSchema.safeParseAsync(values);
if (!validationResult.success) {
validationResult.error.errors.forEach(({ path, message }) => {
(path as Elements[]).forEach((elementName) => {
const removeError = $calculation.element(elementName).setError({ key, message });
if (removeError) validationHelper.add(removeError);
});
});
}
},
{
delay: 100,
}
);
{
const validationHelper = new ValidationHelper();
reaction(
() =>
$calculation.$values.getValues([
'typePTS',
'objectRegistration',
'objectCategoryTax',
'leaseObjectType',
]),
async ({
leaseObjectType: leaseObjectTypeId,
typePTS,
objectRegistration,
objectCategoryTax,
}) => {
if (!leaseObjectTypeId) {
validationHelper.removeErrors();
return;
}
const {
data: { evo_leasingobject_type },
} = await apolloClient.query({
query: CRMTypes.GetLeaseObjectTypeDocument,
variables: { leaseObjectTypeId },
});
$calculation.element('selectObjectCategoryTax').validate({
helper: validationHelper,
invalid:
objectRegistration === 100_000_001 &&
typePTS === 100_000_001 &&
objectCategoryTax === null &&
Boolean(evo_leasingobject_type?.evo_category_tr?.length),
message: 'Необходимо из ЭПТС указать Категорию в соответствии с ТР ТС 018/2011',
});
}
);
}
{
const validationHelper = new ValidationHelper();
reaction(
() => $calculation.$values.getValues(['leaseObjectType', 'insNSIB']),
async ({ insNSIB, leaseObjectType: leaseObjectTypeId }) => {
if (!leaseObjectTypeId) {
validationHelper.removeErrors();
return;
}
const {
data: { evo_leasingobject_type },
} = await apolloClient.query({
query: CRMTypes.GetLeaseObjectTypeDocument,
variables: { leaseObjectTypeId },
});
$calculation.element('selectInsNSIB').validate({
helper: validationHelper,
invalid: evo_leasingobject_type?.evo_id === '11' && !insNSIB,
message: 'Страхование НСИБ обязательно для мотоциклистов',
});
}
);
}
}

View File

@ -0,0 +1,95 @@
/* eslint-disable @typescript-eslint/naming-convention */
import type { ValidationContext } from '../types';
import ValuesSchema from '@/config/schema/values';
import { MAX_MASS } from '@/constants/values';
import * as CRMTypes from '@/graphql/crm.types';
import { z } from 'zod';
export function createValidationSchema({ apolloClient }: ValidationContext) {
return ValuesSchema.pick({
insNSIB: true,
leaseObjectCategory: true,
leaseObjectType: true,
maxMass: true,
objectCategoryTax: true,
objectRegistration: true,
typePTS: true,
vehicleTaxInYear: true,
}).superRefine(
async (
{
leaseObjectCategory,
maxMass,
leaseObjectType: leaseObjectTypeId,
typePTS,
objectRegistration,
objectCategoryTax,
insNSIB,
vehicleTaxInYear,
},
ctx
) => {
if (leaseObjectCategory === 100_000_001 && maxMass > MAX_MASS) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `При категории ТС = В Разрешенная макс.масса не может быть больше ${MAX_MASS} кг`,
path: ['tbxMaxMass'],
});
}
if (leaseObjectCategory === 100_000_002 && maxMass <= MAX_MASS) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `При категории ТС = С Разрешенная макс.масса не может быть меньше ${MAX_MASS} кг`,
path: ['tbxMaxMass'],
});
}
if (leaseObjectTypeId) {
const {
data: { evo_leasingobject_type },
} = await apolloClient.query({
query: CRMTypes.GetLeaseObjectTypeDocument,
variables: { leaseObjectTypeId },
});
if (
objectRegistration === 100_000_001 &&
typePTS === 100_000_001 &&
objectCategoryTax === null &&
Boolean(evo_leasingobject_type?.evo_category_tr?.length)
) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Не заполнено поле',
path: ['selectObjectCategoryTax'],
});
}
if (evo_leasingobject_type?.evo_id === '11' && !insNSIB) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Страхование НСИБ обязательно для мотоциклистов',
path: ['selectInsNSIB'],
});
}
}
if (objectRegistration === 100_000_001 && !(vehicleTaxInYear > 0)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Значение должно быть больше 0',
path: ['tbxVehicleTaxInYear'],
});
}
if (objectRegistration === 100_000_001 && !typePTS) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Не заполнено поле',
path: ['radioTypePTS'],
});
}
}
);
}

View File

@ -1,14 +1,16 @@
/* eslint-disable @typescript-eslint/naming-convention */
import type { ProcessContext } from '../types';
import { normalizeOptions } from '@/../../packages/tools';
import type * as Insurance from '@/Components/Calculation/Form/Insurance/InsuranceTable/types';
import { createValidationSchema } from './validation';
import type { Elements } from '@/Components/Calculation/config/map/values';
import { selectLeaseObjectUseFor } from '@/config/default-options';
import * as CRMTypes from '@/graphql/crm.types';
import ValidationHelper from '@/stores/validation/helper';
import { reaction } from 'mobx';
import { comparer, reaction, toJS } from 'mobx';
import { uid } from 'radash';
import { normalizeOptions } from 'tools';
export function common({ store, apolloClient }: ProcessContext) {
const { $calculation, $tables } = store;
const { $calculation } = store;
reaction(
() => $calculation.element('selectGPSBrand').getValue(),
@ -113,75 +115,52 @@ export function common({ store, apolloClient }: ProcessContext) {
);
}
export function validation({ store, apolloClient }: ProcessContext) {
const { $calculation, $tables } = store;
const key = uid(7);
{
const validationHelper = new ValidationHelper();
reaction(
() => ({
insTerm: $tables.insurance.row('kasko').getValue('insTerm'),
...$calculation.$values.getValues(['leasingPeriod', 'recalcWithRevision', 'quote']),
}),
async ({ leasingPeriod, insTerm, recalcWithRevision, quote: quoteId }) => {
if (!quoteId) {
validationHelper.removeErrors();
export function validation(context: ProcessContext) {
const { $calculation, $tables } = context.store;
return;
}
const validationSchema = createValidationSchema(context);
const helper = new ValidationHelper();
const {
data: { quote },
} = await apolloClient.query({
query: CRMTypes.GetQuoteDocument,
variables: { quoteId },
});
reaction(
() => {
const values = $calculation.$values.getValues([
'leasingPeriod',
'quote',
'recalcWithRevision',
]);
$tables.insurance.validate({
helper: validationHelper,
invalid:
recalcWithRevision &&
quote?.evo_one_year_insurance === true &&
leasingPeriod > 15 &&
insTerm === 100_000_001,
message:
'Срок страхования КАСКО должен быть 12 месяцев, т.к. оформляется Однолетний полис',
return {
insurance: {
fingap: toJS($tables.insurance.row('fingap').getValues()),
kasko: toJS($tables.insurance.row('kasko').getValues()),
osago: toJS($tables.insurance.row('osago').getValues()),
},
...values,
};
},
async (values) => {
helper.removeErrors();
const validationResult = await validationSchema.safeParseAsync(values);
if (!validationResult.success) {
validationResult.error.errors.forEach(({ path, message }) => {
(path as Array<Elements & 'insurance'>).forEach((elementName) => {
if (elementName === 'insurance') {
const removeError = $tables.insurance.setError({ key, message });
if (removeError) helper.add(removeError);
} else {
const removeError = $calculation.element(elementName).setError({ key, message });
if (removeError) helper.add(removeError);
}
});
});
}
);
}
(['osago', 'kasko'] as Insurance.Keys[]).forEach((key) => {
const validationHelper = new ValidationHelper();
reaction(
() => $tables.insurance.row(key).getValues(),
({ insCost, insured, policyType, insuranceCompany, insTerm }) => {
validationHelper.removeErrors();
$tables.insurance.validate({
helper: validationHelper,
invalid: insCost === 0 && insured === 100_000_001,
message: `Укажите стоимость ${policyType}, включаемую в график`,
});
$tables.insurance.validate({
helper: validationHelper,
invalid: insCost > 0 && !insuranceCompany,
message: `Укажите страховую компанию ${policyType}`,
});
$tables.insurance.validate({
helper: validationHelper,
invalid: insCost > 0 && !insTerm,
message: `Укажите срок страхования ${policyType}`,
});
$tables.insurance.validate({
helper: validationHelper,
invalid: insCost > 0 && !insured,
message: `Укажите плательщика ${policyType}`,
});
}
);
});
},
{
delay: 100,
equals: comparer.structural,
}
);
}

View File

@ -0,0 +1,82 @@
import type { ValidationContext } from '../types';
import type * as Insurance from '@/Components/Calculation/Form/Insurance/InsuranceTable/types';
import { RowSchema } from '@/config/schema/insurance';
import ValuesSchema from '@/config/schema/values';
import * as CRMTypes from '@/graphql/crm.types';
import { z } from 'zod';
export function createValidationSchema({ apolloClient }: ValidationContext) {
return ValuesSchema.pick({
leasingPeriod: true,
quote: true,
recalcWithRevision: true,
})
.extend({
insurance: z
.object({
fingap: RowSchema,
kasko: RowSchema,
osago: RowSchema,
})
.strict(),
})
.superRefine(async ({ leasingPeriod, recalcWithRevision, quote: quoteId, insurance }, ctx) => {
if (quoteId) {
const {
data: { quote },
} = await apolloClient.query({
query: CRMTypes.GetQuoteDocument,
variables: { quoteId },
});
if (
recalcWithRevision &&
quote?.evo_one_year_insurance === true &&
leasingPeriod > 15 &&
insurance.kasko.insTerm === 100_000_001
) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message:
'Срок страхования КАСКО должен быть 12 месяцев, т.к. оформляется Однолетний полис',
path: ['insurance'],
});
}
}
(['osago', 'kasko'] as Insurance.Keys[]).forEach((key) => {
const { insCost, insured, policyType, insuranceCompany, insTerm } = insurance[key];
if (insCost === 0 && insured === 100_000_001) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `Укажите стоимость ${policyType}, включаемую в график`,
path: ['insurance'],
});
}
if (insCost > 0 && !insuranceCompany) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `Укажите страховую компанию ${policyType}`,
path: ['insurance'],
});
}
if (insCost > 0 && !insTerm) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `Укажите срок страхования ${policyType}`,
path: ['insurance'],
});
}
if (insCost > 0 && !insured) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `Укажите плательщика ${policyType}`,
path: ['insurance'],
});
}
});
});
}

View File

@ -405,4 +405,42 @@ export default function reactions({ store, apolloClient }: ProcessContext) {
}
}
);
reaction(
() =>
$calculation.$values.getValues([
'leaseObjectType',
'engineVolume',
'engineType',
'leaseObjectMotorPower',
]),
async ({ leaseObjectType: leaseObjectTypeId }) => {
if (!leaseObjectTypeId) {
$calculation.element('selectEngineType').unblock();
$calculation.element('tbxEngineVolume').unblock();
$calculation.element('tbxLeaseObjectMotorPower').unblock();
return;
}
const {
data: { evo_leasingobject_type },
} = await apolloClient.query({
query: CRMTypes.GetLeaseObjectTypeDocument,
variables: {
leaseObjectTypeId,
},
});
if (evo_leasingobject_type?.evo_id === '8') {
$calculation.element('selectEngineType').resetValue().block();
$calculation.element('tbxEngineVolume').resetValue().block();
$calculation.element('tbxLeaseObjectMotorPower').resetValue().block();
} else {
$calculation.element('selectEngineType').unblock();
$calculation.element('tbxEngineVolume').unblock();
$calculation.element('tbxLeaseObjectMotorPower').unblock();
}
}
);
}

View File

@ -1,11 +1,15 @@
/* eslint-disable @typescript-eslint/naming-convention */
import * as CRMTypes from '@/graphql/crm.types';
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 { autorun, reaction } from 'mobx';
import { uid } from 'radash';
export default function reactions({ store, apolloClient }: ProcessContext) {
const { $calculation } = store;
const key = uid(7);
export default function reactions(context: ProcessContext) {
const { $calculation } = context.store;
/**
* Если model содержит данные и по связи Модель-Комплектация в CRM у данной модели есть связанные активные записи Комплектаций,
@ -14,16 +18,23 @@ export default function reactions({ store, apolloClient }: ProcessContext) {
autorun(
() => {
const selectConfiguration = $calculation.element('selectConfiguration');
selectConfiguration.validate({
invalid: selectConfiguration.getOptions()?.length > 0 && !selectConfiguration.getValue(),
message: 'Не заполнено поле',
});
if (selectConfiguration.getOptions()?.length > 0 && !selectConfiguration.getValue()) {
selectConfiguration.setError({
key,
message: 'Не заполнено поле',
});
} else {
selectConfiguration.removeError({ key });
}
},
{
delay: 10,
}
);
const validationSchema = createValidationSchema(context);
const helper = new ValidationHelper();
reaction(
() =>
$calculation.$values.getValues([
@ -31,134 +42,25 @@ export default function reactions({ store, apolloClient }: ProcessContext) {
'engineVolume',
'engineType',
'leaseObjectMotorPower',
'countSeats',
'maxMass',
'leaseObjectCategory',
]),
async ({
engineType,
engineVolume,
leaseObjectType: leaseObjectTypeId,
leaseObjectMotorPower,
}) => {
if (!leaseObjectTypeId) {
$calculation.element('selectEngineType').unblock();
$calculation.element('tbxEngineVolume').unblock();
$calculation.element('tbxLeaseObjectMotorPower').unblock();
async (values) => {
helper.removeErrors();
const validationResult = await validationSchema.safeParseAsync(values);
return;
if (!validationResult.success) {
validationResult.error.errors.forEach(({ path, message }) => {
(path as Elements[]).forEach((elementName) => {
const removeError = $calculation.element(elementName).setError({ key, message });
if (removeError) helper.add(removeError);
});
});
}
const {
data: { evo_leasingobject_type },
} = await apolloClient.query({
query: CRMTypes.GetLeaseObjectTypeDocument,
variables: {
leaseObjectTypeId,
},
});
if (evo_leasingobject_type?.evo_id === '8') {
$calculation.element('selectEngineType').resetValue().block();
$calculation.element('tbxEngineVolume').resetValue().block();
$calculation.element('tbxLeaseObjectMotorPower').resetValue().block();
} else {
$calculation.element('selectEngineType').unblock();
$calculation.element('tbxEngineVolume').unblock();
$calculation.element('tbxLeaseObjectMotorPower').unblock();
}
const isNotTrailer =
evo_leasingobject_type?.evo_id !== null && evo_leasingobject_type?.evo_id !== '8';
$calculation.element('tbxEngineVolume').validate({
invalid: isNotTrailer && engineVolume <= 0,
message: 'Не заполнено поле',
});
$calculation.element('selectEngineType').validate({
invalid: isNotTrailer && !engineType,
message: 'Не заполнено поле',
});
$calculation.element('tbxLeaseObjectMotorPower').validate({
invalid: isNotTrailer && leaseObjectMotorPower <= 0,
message: 'Не заполнено поле',
});
},
{
delay: 100,
}
);
{
const validationHelper = new ValidationHelper();
reaction(
() => $calculation.$values.getValues(['leaseObjectType', 'countSeats', 'maxMass']),
async ({ countSeats, leaseObjectType: leaseObjectTypeId, maxMass }) => {
if (!leaseObjectTypeId) {
validationHelper.removeErrors();
return;
}
const {
data: { evo_leasingobject_type },
} = await apolloClient.query({
query: CRMTypes.GetLeaseObjectTypeDocument,
variables: {
leaseObjectTypeId,
},
});
$calculation.element('tbxCountSeats').validate({
helper: validationHelper,
invalid: evo_leasingobject_type?.evo_id === '1' && countSeats >= 9,
message: 'Количество мест должно быть меньше 9',
});
$calculation.element('tbxCountSeats').validate({
helper: validationHelper,
invalid:
(evo_leasingobject_type?.evo_id === '4' || evo_leasingobject_type?.evo_id === '5') &&
countSeats <= 8,
message: 'Количество мест должно быть больше 8',
});
$calculation.element('tbxMaxMass').validate({
helper: validationHelper,
invalid: evo_leasingobject_type?.evo_id === '2' && maxMass <= 0,
message: 'Не заполнено поле',
});
}
);
}
{
const validationHelper = new ValidationHelper();
reaction(
() => $calculation.$values.getValues(['leaseObjectType', 'leaseObjectCategory']),
async ({ leaseObjectCategory, leaseObjectType: leaseObjectTypeId }) => {
if (!leaseObjectTypeId) {
validationHelper.removeErrors();
return;
}
const {
data: { evo_leasingobject_type },
} = await apolloClient.query({
query: CRMTypes.GetLeaseObjectTypeDocument,
variables: {
leaseObjectTypeId,
},
});
$calculation.element('selectLeaseObjectCategory').validate({
helper: validationHelper,
invalid:
!leaseObjectCategory &&
Boolean(
evo_leasingobject_type?.evo_id &&
!['6', '9', '10'].includes(evo_leasingobject_type?.evo_id)
),
message: 'Не заполнено поле',
});
}
);
}
}

View File

@ -0,0 +1,109 @@
/* eslint-disable @typescript-eslint/naming-convention */
import type { ValidationContext } from '../types';
import ValuesSchema from '@/config/schema/values';
import * as CRMTypes from '@/graphql/crm.types';
import { z } from 'zod';
export function createValidationSchema({ apolloClient }: ValidationContext) {
return ValuesSchema.pick({
countSeats: true,
engineType: true,
engineVolume: true,
leaseObjectCategory: true,
leaseObjectMotorPower: true,
leaseObjectType: true,
maxMass: true,
}).superRefine(
async (
{
leaseObjectType: leaseObjectTypeId,
engineVolume,
engineType,
leaseObjectMotorPower,
countSeats,
maxMass,
leaseObjectCategory,
},
ctx
) => {
if (leaseObjectTypeId) {
const {
data: { evo_leasingobject_type },
} = await apolloClient.query({
query: CRMTypes.GetLeaseObjectTypeDocument,
variables: {
leaseObjectTypeId,
},
});
const isNotTrailer =
evo_leasingobject_type?.evo_id !== null && evo_leasingobject_type?.evo_id !== '8';
if (isNotTrailer && engineVolume <= 0) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Не заполнено поле',
path: ['tbxEngineVolume'],
});
}
if (isNotTrailer && !engineType) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Не заполнено поле',
path: ['selectEngineType'],
});
}
if (isNotTrailer && leaseObjectMotorPower <= 0) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Не заполнено поле',
path: ['tbxLeaseObjectMotorPower'],
});
}
if (evo_leasingobject_type?.evo_id === '1' && countSeats >= 9) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Количество мест должно быть меньше 9',
path: ['tbxCountSeats'],
});
}
if (
(evo_leasingobject_type?.evo_id === '4' || evo_leasingobject_type?.evo_id === '5') &&
countSeats <= 8
) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Количество мест должно быть больше 8',
path: ['tbxCountSeats'],
});
}
if (evo_leasingobject_type?.evo_id === '2' && maxMass <= 0) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Не заполнено поле',
path: ['tbxMaxMass'],
});
}
if (
!leaseObjectCategory &&
Boolean(
evo_leasingobject_type?.evo_id &&
!['6', '9', '10'].includes(evo_leasingobject_type?.evo_id)
)
) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Не заполнено поле',
path: ['selectLeaseObjectCategory'],
});
}
}
}
);
}

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,64 +1,56 @@
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);
}
});
});
}
},
{
delay: 50,
delay: 100,
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,74 +1,45 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { VAT } from '@/constants/values';
import * as CRMTypes from '@/graphql/crm.types';
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 { reaction } from 'mobx';
import { round } from 'tools';
import { uid } from 'radash';
export default function reactions({ store, apolloClient }: ProcessContext) {
const key = uid(7);
export default function reactions(context: ProcessContext) {
const { store } = context;
const { $calculation } = store;
const validationSchema = createValidationSchema(context);
const helper = new ValidationHelper();
reaction(
() =>
$calculation.$values.getValues([
'VATInLeaseObjectPrice',
'leaseObjectPriceWthtVAT',
'product',
'supplierDiscountRub',
'plPriceRub',
'firstPaymentRub',
'subsidySum',
]),
async ({ VATInLeaseObjectPrice, leaseObjectPriceWthtVAT, product: productId }) => {
let evo_sale_without_nds = false;
async (values) => {
helper.removeErrors();
const validationResult = await validationSchema.safeParseAsync(values);
if (productId) {
const {
data: { evo_baseproduct },
} = await apolloClient.query({
query: CRMTypes.GetProductDocument,
variables: {
productId,
},
if (!validationResult.success) {
validationResult.error.errors.forEach(({ path, message }) => {
(path as Elements[]).forEach((elementName) => {
const removeError = $calculation.element(elementName).setError({ key, message });
if (removeError) helper.add(removeError);
});
});
if (evo_baseproduct?.evo_sale_without_nds) {
evo_sale_without_nds = evo_baseproduct.evo_sale_without_nds;
}
}
$calculation.element('tbxVATInLeaseObjectPrice').validate({
invalid:
evo_sale_without_nds && round(VATInLeaseObjectPrice / leaseObjectPriceWthtVAT, 2) >= VAT,
message:
'При продаже ПЛ после ФЛ размер НДС в стоимости ПЛ не может составлять 20% и более от стоимости с НДС. Проверьте корректность НДС, либо измените Продукт',
});
}
);
reaction(
() => $calculation.$values.getValues(['supplierDiscountRub', 'plPriceRub']),
({ supplierDiscountRub, plPriceRub }) => {
$calculation.element('tbxSupplierDiscountRub').validate({
invalid: supplierDiscountRub >= plPriceRub,
message: 'Скидка от поставщика не может быть больше или равна стоимости ПЛ',
});
}
);
reaction(
() => $calculation.$values.getValues(['firstPaymentRub', 'plPriceRub']),
({ firstPaymentRub, plPriceRub }) => {
$calculation.element('tbxFirstPaymentRub').validate({
invalid: firstPaymentRub >= plPriceRub,
message: 'Первый платеж не может быть больше или равен стоимости ПЛ',
});
}
);
reaction(
() => $calculation.$values.getValues(['firstPaymentRub', 'subsidySum']),
({ firstPaymentRub, subsidySum }) => {
$calculation.element('tbxFirstPaymentRub').validate({
invalid: firstPaymentRub - subsidySum < 0,
message:
'Первый платеж с учетом субсидии получается отрицательный, увеличьте первый платеж',
});
},
{
delay: 100,
}
);
}

View File

@ -0,0 +1,80 @@
/* eslint-disable @typescript-eslint/naming-convention */
import type { ValidationContext } from '../types';
import ValuesSchema from '@/config/schema/values';
import { VAT } from '@/constants/values';
import * as CRMTypes from '@/graphql/crm.types';
import { round } from 'tools';
import { z } from 'zod';
export function createValidationSchema({ apolloClient }: ValidationContext) {
return ValuesSchema.pick({
VATInLeaseObjectPrice: true,
firstPaymentRub: true,
leaseObjectPriceWthtVAT: true,
plPriceRub: true,
product: true,
subsidySum: true,
supplierDiscountRub: true,
}).superRefine(
async (
{
VATInLeaseObjectPrice,
leaseObjectPriceWthtVAT,
product: productId,
supplierDiscountRub,
plPriceRub,
firstPaymentRub,
subsidySum,
},
ctx
) => {
if (productId) {
const {
data: { evo_baseproduct },
} = await apolloClient.query({
query: CRMTypes.GetProductDocument,
variables: {
productId,
},
});
if (
evo_baseproduct?.evo_sale_without_nds &&
round(VATInLeaseObjectPrice / leaseObjectPriceWthtVAT, 2) >= VAT
) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message:
'При продаже ПЛ после ФЛ размер НДС в стоимости ПЛ не может составлять 20% и более от стоимости с НДС. Проверьте корректность НДС, либо измените Продукт',
path: ['tbxVATInLeaseObjectPrice'],
});
}
}
if (supplierDiscountRub >= plPriceRub) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Скидка не может быть больше или равна стоимости ПЛ',
path: ['tbxSupplierDiscountRub'],
});
}
if (firstPaymentRub >= plPriceRub) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Первый платеж не может быть больше или равен стоимости ПЛ',
path: ['tbxFirstPaymentRub'],
});
}
if (firstPaymentRub - subsidySum < 0) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message:
'Первый платеж с учетом субсидии получается отрицательный, увеличьте первый платеж',
path: ['tbxFirstPaymentRub'],
});
}
}
);
}

View File

@ -2,11 +2,10 @@
import type { AgentsFields, AgentsRewardConditionsFields, AgentsSumFields } from './types';
import * as CRMTypes from '@/graphql/crm.types';
import type RootStore from '@/stores/root';
import ValidationHelper from '@/stores/validation/helper';
import type { ApolloClient } from '@apollo/client';
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import { autorun, reaction } from 'mobx';
import { reaction } from 'mobx';
import { normalizeOptions } from 'tools/entity';
import { makeDisposable } from 'tools/mobx';
@ -104,108 +103,3 @@ export function fillAgentRewardSummReaction(
}
);
}
export function validateAgentRewardSumm(
store: RootStore,
apolloClient: ApolloClient<object>,
agentParams: {
rewardConditionField: AgentsRewardConditionsFields;
rewardSummField: AgentsSumFields;
}
) {
const { $calculation } = store;
const { rewardConditionField, rewardSummField } = agentParams;
const validationHelper = new ValidationHelper();
reaction(
() => $calculation.element(rewardSummField).getValue(),
async (rewardSumm) => {
const conditionId = $calculation.element(rewardConditionField).getValue();
if (!conditionId) {
validationHelper.removeErrors();
return;
}
const {
data: { evo_reward_condition },
} = await apolloClient.query<
CRMTypes.GetRewardConditionQuery,
CRMTypes.GetRewardConditionQueryVariables
>({
query: CRMTypes.GetRewardConditionDocument,
variables: {
conditionId,
},
});
if (!evo_reward_condition) {
validationHelper.removeErrors();
return;
}
if (evo_reward_condition.evo_reward_summ) {
$calculation.element(rewardSummField).validate({
helper: validationHelper,
invalid: rewardSumm > evo_reward_condition?.evo_reward_summ,
message: 'Вознаграждение указано больше условия по агентскому договору!',
});
}
if (
evo_reward_condition?.evo_reduce_reward !== null &&
evo_reward_condition.evo_reward_summ
) {
$calculation.element(rewardSummField).validate({
helper: validationHelper,
invalid:
evo_reward_condition.evo_reduce_reward === false &&
rewardSumm < evo_reward_condition.evo_reward_summ,
message: 'Вознаграждение указано меньше условия по агентскому договору!',
});
}
if (evo_reward_condition?.evo_min_reward_summ !== null) {
$calculation.element(rewardSummField).validate({
helper: validationHelper,
invalid: rewardSumm < evo_reward_condition?.evo_min_reward_summ,
message: 'Вознаграждение указано меньше условия по агентскому договору!',
});
}
}
);
autorun(() => {
const rewardSumm = $calculation.element(rewardSummField).getValue();
const rewardConditionOptions = $calculation.$options.getOptions(rewardConditionField);
const conditionIds = rewardConditionOptions.map((x) => x.value);
const requests = conditionIds.map(async (conditionId) => {
const {
data: { evo_reward_condition },
} = await apolloClient.query<
CRMTypes.GetRewardConditionQuery,
CRMTypes.GetRewardConditionQueryVariables
>({
query: CRMTypes.GetRewardConditionDocument,
variables: {
conditionId,
},
});
return evo_reward_condition;
});
Promise.all(requests).then((results) => {
const required_reward = results.some(
(x) => x?.evo_agency_agreementidData?.evo_required_reward === true
);
$calculation.element(rewardSummField).validate({
invalid: rewardSumm === 0 && required_reward,
message: 'Согласно агентскому договору обязательна выплата АВ. Заложите АВ в расчет',
});
});
});
}

View File

@ -6,12 +6,11 @@ import * as CRMTypes from '@/graphql/crm.types';
import type { ProcessContext } from '@/process/types';
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import { autorun, reaction } from 'mobx';
import { reaction } from 'mobx';
import { makeDisposable } from 'tools/mobx';
const { fillIndAgent, fillCalcBroker, fillCalcDoubleAgent, fillFinDepartment } = fillAgentsFromLead;
const { fillAgentRewardReaction, fillAgentRewardSummReaction, validateAgentRewardSumm } =
createReactions;
const { fillAgentRewardReaction, fillAgentRewardSummReaction } = createReactions;
dayjs.extend(utc);
@ -521,165 +520,3 @@ export function common({ store, apolloClient }: ProcessContext) {
);
}
}
export function validation({ store, apolloClient }: ProcessContext) {
const { $calculation } = store;
validateAgentRewardSumm(store, apolloClient, {
rewardConditionField: 'selectIndAgentRewardCondition',
rewardSummField: 'tbxIndAgentRewardSumm',
});
validateAgentRewardSumm(store, apolloClient, {
rewardConditionField: 'selectCalcDoubleAgentRewardCondition',
rewardSummField: 'tbxCalcDoubleAgentRewardSumm',
});
validateAgentRewardSumm(store, apolloClient, {
rewardConditionField: 'selectCalcBrokerRewardCondition',
rewardSummField: 'tbxCalcBrokerRewardSum',
});
validateAgentRewardSumm(store, apolloClient, {
rewardConditionField: 'selectFinDepartmentRewardCondtion',
rewardSummField: 'tbxFinDepartmentRewardSumm',
});
/**
* Добавить валидацию на кнопку Рассчитать:
* если tbxDealerRewardSumm > 0 и
* если selectDealerPerson = selectDealerBroker и tbxDealerBrokerRewardSumm > 0, то ругаться на selectDealerPerson
* если selectDealerPerson = selectIndAgent и tbxIndAgentRewardSumm > 0, то ругаться на selectDealerPerson
* если selectDealerPerson = selectCalcDoubleAgent и tbxCalcDoubleAgentRewardSumm > 0, то ругаться на selectDealerPerson
* если selectDealerPerson = selectCalcBroker tbxCalcBrokerRewardSum > 0, то ругаться на selectDealerPerson
* если selectDealerPerson = selectFinDepartment и tbxFinDepartmentRewardSumm > 0, то ругаться на selectDealerPerson
* 2.если tbxDealerBrokerRewardSumm > 0 и
*
* если selectDealerBroker = selectDealerPerson и tbxDealerRewardSumm > 0, то ругаться на selectDealerBroker
* если selectDealerBroker = selectIndAgent и tbxIndAgentRewardSumm > 0, то ругаться на selectDealerBroker
* если selectDealerBroker = selectCalcDoubleAgent и tbxCalcDoubleAgentRewardSumm > 0, то ругаться на selectDealerBroker
* если selectDealerBroker = selectCalcBroker tbxCalcBrokerRewardSum > 0, то ругаться на selectDealerBroker
* если selectDealerBroker = selectFinDepartment и tbxFinDepartmentRewardSumm > 0, то ругаться на selectDealerBroker
* 3. если tbxIndAgentRewardSumm > 0 и
*
* если selectIndAgent = selectDealerPerson и tbxDealerRewardSumm > 0, то ругаться на selectIndAgent
* если selectIndAgent = selectDealerBroker и tbxDealerBrokerRewardSumm > 0, то ругаться на selectIndAgent
* если selectIndAgent = selectCalcDoubleAgent и tbxCalcDoubleAgentRewardSumm > 0, то ругаться на selectIndAgent
* если selectIndAgent = selectCalcBroker tbxCalcBrokerRewardSum > 0, то ругаться на selectIndAgent
* если selectIndAgent = selectFinDepartment и tbxFinDepartmentRewardSumm > 0, то ругаться на selectIndAgent
* 4. если tbxCalcDoubleAgentRewardSumm > 0 и
*
* если selectCalcDoubleAgent = selectDealerPerson и tbxDealerRewardSumm > 0, то ругаться на selectCalcDoubleAgent
* если selectCalcDoubleAgent = selectDealerBroker и tbxDealerBrokerRewardSumm > 0, то ругаться на selectCalcDoubleAgent
* если selectCalcDoubleAgent = sselectIndAgent и tbxIndAgentRewardSumm > 0, то ругаться на selectCalcDoubleAgent
* если selectCalcDoubleAgent = selectCalcBroker tbxCalcBrokerRewardSum > 0, то ругаться на selectCalcDoubleAgent
* если selectCalcDoubleAgent = selectFinDepartment и tbxFinDepartmentRewardSumm > 0, то ругаться на selectCalcDoubleAgent
* 5. если tbxCalcBrokerRewardSum > 0 и
*
* если selectCalcBroker = selectDealerPerson и tbxDealerRewardSumm > 0, то ругаться на selectCalcBroker
* если selectCalcBroker = selectDealerBroker и tbxDealerBrokerRewardSumm > 0, то ругаться на selectCalcBroker
* если selectCalcBroker = sselectIndAgent и tbxIndAgentRewardSumm > 0, то ругаться на selectCalcBroker
* если selectCalcBroker = selectCalcDoubleAgent и tbxCalcDoubleAgentRewardSumm > 0, то ругаться на selectCalcBroker
* если selectCalcBroker = selectFinDepartment и tbxFinDepartmentRewardSumm > 0, то ругаться на selectCalcBroker
* 6. если tbxFinDepartmentRewardSumm > 0 и
*
* если selectFinDepartment = selectDealerPerson и tbxDealerRewardSumm > 0, то ругаться на selectFinDepartment
* если selectFinDepartment = selectDealerBroker и tbxDealerBrokerRewardSumm > 0, то ругаться на selectFinDepartment
* если selectFinDepartment = sselectIndAgent и tbxIndAgentRewardSumm > 0, то ругаться на selectFinDepartment
* если selectFinDepartment = selectCalcDoubleAgent и tbxCalcDoubleAgentRewardSumm > 0, то ругаться на selectFinDepartment
* если selectFinDepartment = selectCalcBroker tbxCalcBrokerRewardSum > 0, то ругаться на selectFinDepartment
*/
// eslint-disable-next-line complexity, sonarjs/cognitive-complexity
autorun(() => {
const dealerRewardSumm = $calculation.element('tbxDealerRewardSumm').getValue();
const dealerBrokerRewardSumm = $calculation.element('tbxDealerBrokerRewardSumm').getValue();
const indAgentRewardSumm = $calculation.element('tbxIndAgentRewardSumm').getValue();
const calcDoubleAgentRewardSumm = $calculation
.element('tbxCalcDoubleAgentRewardSumm')
.getValue();
const calcBrokerRewardSum = $calculation.element('tbxCalcBrokerRewardSum').getValue();
const finDepartmentRewardSumm = $calculation.element('tbxFinDepartmentRewardSumm').getValue();
const dealerPerson = $calculation.element('selectDealerPerson').getValue();
const dealerBroker = $calculation.element('selectDealerBroker').getValue();
const indAgent = $calculation.element('selectIndAgent').getValue();
const calcDoubleAgent = $calculation.element('selectCalcDoubleAgent').getValue();
const calcBroker = $calculation.element('selectCalcBroker').getValue();
const calcFinDepartment = $calculation.element('selectCalcFinDepartment').getValue();
const message = 'Вы закладываете вознаграждение одному и тому же агенту дважды';
$calculation.element('selectDealerPerson').validate({
invalid:
dealerRewardSumm > 0 &&
Boolean(dealerPerson) &&
((dealerPerson === dealerBroker && dealerBrokerRewardSumm > 0) ||
(dealerPerson === indAgent && indAgentRewardSumm > 0) ||
(dealerPerson === calcDoubleAgent && calcDoubleAgentRewardSumm > 0) ||
(dealerPerson === calcBroker && calcBrokerRewardSum > 0) ||
(dealerPerson === calcFinDepartment && finDepartmentRewardSumm > 0)),
message,
});
$calculation.element('selectDealerBroker').validate({
invalid:
dealerBrokerRewardSumm > 0 &&
Boolean(dealerBroker) &&
((dealerBroker === dealerPerson && dealerRewardSumm > 0) ||
(dealerBroker === indAgent && indAgentRewardSumm > 0) ||
(dealerBroker === calcDoubleAgent && calcDoubleAgentRewardSumm > 0) ||
(dealerBroker === calcBroker && calcBrokerRewardSum > 0) ||
(dealerBroker === calcFinDepartment && finDepartmentRewardSumm > 0)),
message,
});
$calculation.element('selectIndAgent').validate({
invalid:
indAgentRewardSumm > 0 &&
Boolean(indAgent) &&
((indAgent === dealerPerson && dealerRewardSumm > 0) ||
(indAgent === dealerBroker && dealerBrokerRewardSumm > 0) ||
(indAgent === calcDoubleAgent && calcDoubleAgentRewardSumm > 0) ||
(indAgent === calcBroker && calcBrokerRewardSum > 0) ||
(indAgent === calcFinDepartment && finDepartmentRewardSumm > 0)),
message,
});
$calculation.element('selectCalcDoubleAgent').validate({
invalid:
calcDoubleAgentRewardSumm > 0 &&
Boolean(calcDoubleAgent) &&
((calcDoubleAgent === dealerPerson && dealerRewardSumm > 0) ||
(calcDoubleAgent === dealerBroker && dealerBrokerRewardSumm > 0) ||
(calcDoubleAgent === indAgent && indAgentRewardSumm > 0) ||
(calcDoubleAgent === calcBroker && calcBrokerRewardSum > 0) ||
(calcDoubleAgent === calcFinDepartment && finDepartmentRewardSumm > 0)),
message,
});
$calculation.element('selectCalcBroker').validate({
invalid:
calcBrokerRewardSum > 0 &&
Boolean(calcBroker) &&
((calcBroker === dealerPerson && dealerRewardSumm > 0) ||
(calcBroker === dealerBroker && dealerBrokerRewardSumm > 0) ||
(calcBroker === indAgent && indAgentRewardSumm > 0) ||
(calcBroker === calcDoubleAgent && calcDoubleAgentRewardSumm > 0) ||
(calcBroker === calcFinDepartment && finDepartmentRewardSumm > 0)),
message,
});
$calculation.element('selectCalcFinDepartment').validate({
invalid:
finDepartmentRewardSumm > 0 &&
Boolean(calcFinDepartment) &&
((calcFinDepartment === dealerPerson && dealerRewardSumm > 0) ||
(calcFinDepartment === dealerBroker && dealerBrokerRewardSumm > 0) ||
(calcFinDepartment === indAgent && indAgentRewardSumm > 0) ||
(calcFinDepartment === calcDoubleAgent && calcDoubleAgentRewardSumm > 0) ||
(calcFinDepartment === calcBroker && calcBrokerRewardSum > 0)),
message,
});
});
}

View File

@ -9,9 +9,6 @@ function common(context: ProcessContext) {
supplier.common(context);
}
function validation(context: ProcessContext) {
agents.validation(context);
supplier.validation(context);
}
export { common };
export { common, validation };
export { default as validation } from './validation';

View File

@ -1,6 +1,6 @@
import * as CRMTypes from '@/graphql/crm.types';
import type { ProcessContext } from '@/process/types';
import { autorun, reaction } from 'mobx';
import { reaction } from 'mobx';
export function common({ store, apolloClient }: ProcessContext) {
const { $calculation, $tables } = store;
@ -73,37 +73,4 @@ export function common({ store, apolloClient }: ProcessContext) {
}
}
);
/**
* В валидацию на кнопку Рассчитать внести изменение:
* 1) поле selectDealerPerson убрать из списка обязательных для расчета полей
* 2) добавить валидацию на поле selectDealerPerson :
* Если в поле selectDealer указан account, у которого evo_return_leasing_dealer = False (или null)
* и поле selectDealerPerson = null, то выводить ошибку и поле selectDealerPerson обводить красной рамкой,
* иначе все ок
*/
autorun(async () => {
const dealerId = $calculation.element('selectDealer').getValue();
const dealerPersonId = $calculation.element('selectDealerPerson').getValue();
let returnLeasing: boolean | null | undefined;
if (dealerId) {
const {
data: { dealer },
} = await apolloClient.query({
query: CRMTypes.GetDealerDocument,
variables: {
dealerId,
},
});
returnLeasing = dealer?.evo_return_leasing_dealer;
}
$calculation.element('selectDealerPerson').validate({
invalid: Boolean(dealerId) && !dealerPersonId && !returnLeasing,
message: 'Не заполнено поле',
});
});
}

View File

@ -157,15 +157,3 @@ export function common({ store, apolloClient }: ProcessContext) {
}
);
}
export function validation({ store, apolloClient }: ProcessContext) {
createReactions.validateAgentRewardSumm(store, apolloClient, {
rewardConditionField: 'selectDealerRewardCondition',
rewardSummField: 'tbxDealerRewardSumm',
});
createReactions.validateAgentRewardSumm(store, apolloClient, {
rewardConditionField: 'selectDealerBrokerRewardCondition',
rewardSummField: 'tbxDealerBrokerRewardSumm',
});
}

View File

@ -0,0 +1,70 @@
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 { reaction } from 'mobx';
import { uid } from 'radash';
const key = uid(7);
export default function reactions(context: ProcessContext) {
const { store } = context;
const { $calculation } = store;
const validationSchema = createValidationSchema(context);
const helper = new ValidationHelper();
reaction(
() => {
const values = $calculation.$values.getValues([
'calcBrokerRewardCondition',
'calcBrokerRewardSum',
'calcDoubleAgentRewardCondition',
'calcDoubleAgentRewardSumm',
'dealer',
'dealerBroker',
'calcFinDepartment',
'dealerBrokerRewardCondition',
'dealerBrokerRewardSumm',
'dealerPerson',
'dealerRewardCondition',
'dealerRewardSumm',
'finDepartmentRewardCondtion',
'finDepartmentRewardSumm',
'indAgent',
'indAgentRewardCondition',
'indAgentRewardSumm',
'calcDoubleAgent',
'calcBroker',
]);
const options = (
[
'selectCalcBrokerRewardCondition',
'selectCalcDoubleAgentRewardCondition',
'selectDealerBrokerRewardCondition',
'selectDealerRewardCondition',
'selectFinDepartmentRewardCondtion',
'selectIndAgentRewardCondition',
] as Elements[]
).map((elementName) => $calculation.element(elementName).getOptions());
return { options, values };
},
async ({ values }) => {
helper.removeErrors();
const validationResult = await validationSchema.safeParseAsync(values);
if (!validationResult.success) {
validationResult.error.errors.forEach(({ path, message }) => {
(path as Elements[]).forEach((elementName) => {
const removeError = $calculation.element(elementName).setError({ key, message });
if (removeError) helper.add(removeError);
});
});
}
},
{
delay: 100,
}
);
}

View File

@ -0,0 +1,388 @@
/* eslint-disable zod/require-strict */
/* eslint-disable @typescript-eslint/naming-convention */
/* eslint-disable complexity */
import type { ValidationContext } from '../types';
import type { Elements } from '@/Components/Calculation/config/map/values';
import ValuesSchema from '@/config/schema/values';
import * as CRMTypes from '@/graphql/crm.types';
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import { normalizeOptions } from 'tools';
import type { RefinementCtx } from 'zod';
import { z } from 'zod';
dayjs.extend(utc);
function helper({ apolloClient, ctx }: ValidationContext & { ctx: RefinementCtx }) {
return {
async validateRewardSum({
agentid,
conditionId,
sumFieldName,
sum,
}: {
agentid: string | null;
conditionId: string | null;
sum: number;
sumFieldName: Elements;
}) {
if (agentid) {
const {
data: { evo_reward_conditions },
} = await apolloClient.query({
query: CRMTypes.GetRewardConditionsDocument,
variables: {
agentid,
currentDate: dayjs().utc(false).format('YYYY-MM-DD'),
},
});
const requests = normalizeOptions(evo_reward_conditions)?.map(async ({ value }) => {
const {
data: { evo_reward_condition },
} = await apolloClient.query<
CRMTypes.GetRewardConditionQuery,
CRMTypes.GetRewardConditionQueryVariables
>({
query: CRMTypes.GetRewardConditionDocument,
variables: {
conditionId: value,
},
});
return evo_reward_condition;
});
const requiredReward = (await Promise.all(requests)).some(
(x) => x?.evo_agency_agreementidData?.evo_required_reward === true
);
if (sum === 0 && requiredReward) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Согласно агентскому договору обязательна выплата АВ. Заложите АВ в расчет',
path: [sumFieldName],
});
}
}
if (conditionId) {
const {
data: { evo_reward_condition },
} = await apolloClient.query<
CRMTypes.GetRewardConditionQuery,
CRMTypes.GetRewardConditionQueryVariables
>({
query: CRMTypes.GetRewardConditionDocument,
variables: {
conditionId,
},
});
if (evo_reward_condition?.evo_reward_summ && sum > evo_reward_condition?.evo_reward_summ) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Вознаграждение указано больше условия по агентскому договору!',
path: [sumFieldName],
});
}
if (
evo_reward_condition?.evo_reduce_reward === false &&
evo_reward_condition?.evo_reward_summ &&
sum < evo_reward_condition?.evo_reward_summ
) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Вознаграждение указано меньше условия по агентскому договору!',
path: [sumFieldName],
});
}
if (
evo_reward_condition?.evo_min_reward_summ &&
sum < evo_reward_condition?.evo_min_reward_summ
) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Вознаграждение указано меньше условия по агентскому договору!',
path: [sumFieldName],
});
}
}
},
};
}
const ERR_DOUBLE_REWARD = 'Вы закладываете вознаграждение одному и тому же агенту дважды';
const AgentsSchema = ValuesSchema.pick({
calcBroker: true,
calcDoubleAgent: true,
calcFinDepartment: true,
dealer: true,
dealerBroker: true,
dealerPerson: true,
indAgent: true,
});
const ConditionsSchema = ValuesSchema.pick({
calcBrokerRewardCondition: true,
calcDoubleAgentRewardCondition: true,
dealerBrokerRewardCondition: true,
dealerRewardCondition: true,
finDepartmentRewardCondtion: true,
indAgentRewardCondition: true,
});
const SumsSchema = ValuesSchema.pick({
calcBrokerRewardSum: true,
calcDoubleAgentRewardSumm: true,
dealerBrokerRewardSumm: true,
dealerRewardSumm: true,
finDepartmentRewardSumm: true,
indAgentRewardSumm: true,
});
export function createValidationSchema(context: ValidationContext) {
const { apolloClient } = context;
return z
.object({})
.merge(AgentsSchema)
.merge(ConditionsSchema)
.merge(SumsSchema)
.superRefine(async (values, ctx) => {
const {
calcBrokerRewardCondition,
calcBrokerRewardSum,
calcDoubleAgentRewardCondition,
calcDoubleAgentRewardSumm,
dealer: dealerId,
dealerBroker,
calcFinDepartment,
dealerBrokerRewardCondition,
dealerBrokerRewardSumm,
dealerPerson,
dealerRewardCondition,
dealerRewardSumm,
finDepartmentRewardCondtion,
finDepartmentRewardSumm,
indAgent,
indAgentRewardCondition,
indAgentRewardSumm,
calcDoubleAgent,
calcBroker,
} = values;
/**
* Добавить валидацию на кнопку Рассчитать:
* если tbxDealerRewardSumm > 0 и
* если selectDealerPerson = selectDealerBroker и tbxDealerBrokerRewardSumm > 0, то ругаться на selectDealerPerson
* если selectDealerPerson = selectIndAgent и tbxIndAgentRewardSumm > 0, то ругаться на selectDealerPerson
* если selectDealerPerson = selectCalcDoubleAgent и tbxCalcDoubleAgentRewardSumm > 0, то ругаться на selectDealerPerson
* если selectDealerPerson = selectCalcBroker tbxCalcBrokerRewardSum > 0, то ругаться на selectDealerPerson
* если selectDealerPerson = selectFinDepartment и tbxFinDepartmentRewardSumm > 0, то ругаться на selectDealerPerson
* 2.если tbxDealerBrokerRewardSumm > 0 и
*
* если selectDealerBroker = selectDealerPerson и tbxDealerRewardSumm > 0, то ругаться на selectDealerBroker
* если selectDealerBroker = selectIndAgent и tbxIndAgentRewardSumm > 0, то ругаться на selectDealerBroker
* если selectDealerBroker = selectCalcDoubleAgent и tbxCalcDoubleAgentRewardSumm > 0, то ругаться на selectDealerBroker
* если selectDealerBroker = selectCalcBroker tbxCalcBrokerRewardSum > 0, то ругаться на selectDealerBroker
* если selectDealerBroker = selectFinDepartment и tbxFinDepartmentRewardSumm > 0, то ругаться на selectDealerBroker
* 3. если tbxIndAgentRewardSumm > 0 и
*
* если selectIndAgent = selectDealerPerson и tbxDealerRewardSumm > 0, то ругаться на selectIndAgent
* если selectIndAgent = selectDealerBroker и tbxDealerBrokerRewardSumm > 0, то ругаться на selectIndAgent
* если selectIndAgent = selectCalcDoubleAgent и tbxCalcDoubleAgentRewardSumm > 0, то ругаться на selectIndAgent
* если selectIndAgent = selectCalcBroker tbxCalcBrokerRewardSum > 0, то ругаться на selectIndAgent
* если selectIndAgent = selectFinDepartment и tbxFinDepartmentRewardSumm > 0, то ругаться на selectIndAgent
* 4. если tbxCalcDoubleAgentRewardSumm > 0 и
*
* если selectCalcDoubleAgent = selectDealerPerson и tbxDealerRewardSumm > 0, то ругаться на selectCalcDoubleAgent
* если selectCalcDoubleAgent = selectDealerBroker и tbxDealerBrokerRewardSumm > 0, то ругаться на selectCalcDoubleAgent
* если selectCalcDoubleAgent = sselectIndAgent и tbxIndAgentRewardSumm > 0, то ругаться на selectCalcDoubleAgent
* если selectCalcDoubleAgent = selectCalcBroker tbxCalcBrokerRewardSum > 0, то ругаться на selectCalcDoubleAgent
* если selectCalcDoubleAgent = selectFinDepartment и tbxFinDepartmentRewardSumm > 0, то ругаться на selectCalcDoubleAgent
* 5. если tbxCalcBrokerRewardSum > 0 и
*
* если selectCalcBroker = selectDealerPerson и tbxDealerRewardSumm > 0, то ругаться на selectCalcBroker
* если selectCalcBroker = selectDealerBroker и tbxDealerBrokerRewardSumm > 0, то ругаться на selectCalcBroker
* если selectCalcBroker = sselectIndAgent и tbxIndAgentRewardSumm > 0, то ругаться на selectCalcBroker
* если selectCalcBroker = selectCalcDoubleAgent и tbxCalcDoubleAgentRewardSumm > 0, то ругаться на selectCalcBroker
* если selectCalcBroker = selectFinDepartment и tbxFinDepartmentRewardSumm > 0, то ругаться на selectCalcBroker
* 6. если tbxFinDepartmentRewardSumm > 0 и
*
* если selectFinDepartment = selectDealerPerson и tbxDealerRewardSumm > 0, то ругаться на selectFinDepartment
* если selectFinDepartment = selectDealerBroker и tbxDealerBrokerRewardSumm > 0, то ругаться на selectFinDepartment
* если selectFinDepartment = sselectIndAgent и tbxIndAgentRewardSumm > 0, то ругаться на selectFinDepartment
* если selectFinDepartment = selectCalcDoubleAgent и tbxCalcDoubleAgentRewardSumm > 0, то ругаться на selectFinDepartment
* если selectFinDepartment = selectCalcBroker tbxCalcBrokerRewardSum > 0, то ругаться на selectFinDepartment
*/
if (
dealerRewardSumm > 0 &&
Boolean(dealerPerson) &&
((dealerPerson === dealerBroker && dealerBrokerRewardSumm > 0) ||
(dealerPerson === indAgent && indAgentRewardSumm > 0) ||
(dealerPerson === calcDoubleAgent && calcDoubleAgentRewardSumm > 0) ||
(dealerPerson === calcBroker && calcBrokerRewardSum > 0) ||
(dealerPerson === calcFinDepartment && finDepartmentRewardSumm > 0))
) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: ERR_DOUBLE_REWARD,
path: ['selectDealerPerson'],
});
}
if (
dealerBrokerRewardSumm > 0 &&
Boolean(dealerBroker) &&
((dealerBroker === dealerPerson && dealerRewardSumm > 0) ||
(dealerBroker === indAgent && indAgentRewardSumm > 0) ||
(dealerBroker === calcDoubleAgent && calcDoubleAgentRewardSumm > 0) ||
(dealerBroker === calcBroker && calcBrokerRewardSum > 0) ||
(dealerBroker === calcFinDepartment && finDepartmentRewardSumm > 0))
) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: ERR_DOUBLE_REWARD,
path: ['selectDealerBroker'],
});
}
if (
indAgentRewardSumm > 0 &&
Boolean(indAgent) &&
((indAgent === dealerPerson && dealerRewardSumm > 0) ||
(indAgent === dealerBroker && dealerBrokerRewardSumm > 0) ||
(indAgent === calcDoubleAgent && calcDoubleAgentRewardSumm > 0) ||
(indAgent === calcBroker && calcBrokerRewardSum > 0) ||
(indAgent === calcFinDepartment && finDepartmentRewardSumm > 0))
) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: ERR_DOUBLE_REWARD,
path: ['selectIndAgent'],
});
}
if (
calcDoubleAgentRewardSumm > 0 &&
Boolean(calcDoubleAgent) &&
((calcDoubleAgent === dealerPerson && dealerRewardSumm > 0) ||
(calcDoubleAgent === dealerBroker && dealerBrokerRewardSumm > 0) ||
(calcDoubleAgent === indAgent && indAgentRewardSumm > 0) ||
(calcDoubleAgent === calcBroker && calcBrokerRewardSum > 0) ||
(calcDoubleAgent === calcFinDepartment && finDepartmentRewardSumm > 0))
) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: ERR_DOUBLE_REWARD,
path: ['selectCalcDoubleAgent'],
});
}
if (
calcBrokerRewardSum > 0 &&
Boolean(calcBroker) &&
((calcBroker === dealerPerson && dealerRewardSumm > 0) ||
(calcBroker === dealerBroker && dealerBrokerRewardSumm > 0) ||
(calcBroker === indAgent && indAgentRewardSumm > 0) ||
(calcBroker === calcDoubleAgent && calcDoubleAgentRewardSumm > 0) ||
(calcBroker === calcFinDepartment && finDepartmentRewardSumm > 0))
) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: ERR_DOUBLE_REWARD,
path: ['selectCalcBroker'],
});
}
if (
finDepartmentRewardSumm > 0 &&
Boolean(calcFinDepartment) &&
((calcFinDepartment === dealerPerson && dealerRewardSumm > 0) ||
(calcFinDepartment === dealerBroker && dealerBrokerRewardSumm > 0) ||
(calcFinDepartment === indAgent && indAgentRewardSumm > 0) ||
(calcFinDepartment === calcDoubleAgent && calcDoubleAgentRewardSumm > 0) ||
(calcFinDepartment === calcBroker && calcBrokerRewardSum > 0))
) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: ERR_DOUBLE_REWARD,
path: ['selectCalcFinDepartment'],
});
}
/**
* В валидацию на кнопку Рассчитать внести изменение:
* 1) поле selectDealerPerson убрать из списка обязательных для расчета полей
* 2) добавить валидацию на поле selectDealerPerson :
* Если в поле selectDealer указан account, у которого evo_return_leasing_dealer = False (или null)
* и поле selectDealerPerson = null, то выводить ошибку и поле selectDealerPerson обводить красной рамкой,
* иначе все ок
*/
if (dealerId) {
const {
data: { dealer },
} = await apolloClient.query({
query: CRMTypes.GetDealerDocument,
variables: {
dealerId,
},
});
if (!dealerPerson && !dealer?.evo_return_leasing_dealer)
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Не заполнено поле',
path: ['selectDealerPerson'],
});
}
const { validateRewardSum } = helper({ ...context, ctx });
await validateRewardSum({
agentid: dealerPerson,
conditionId: dealerRewardCondition,
sum: dealerRewardSumm,
sumFieldName: 'tbxDealerRewardSumm',
});
await validateRewardSum({
agentid: dealerBroker,
conditionId: dealerBrokerRewardCondition,
sum: dealerBrokerRewardSumm,
sumFieldName: 'tbxDealerBrokerRewardSumm',
});
await validateRewardSum({
agentid: indAgent,
conditionId: indAgentRewardCondition,
sum: indAgentRewardSumm,
sumFieldName: 'tbxIndAgentRewardSumm',
});
await validateRewardSum({
agentid: calcDoubleAgent,
conditionId: calcDoubleAgentRewardCondition,
sum: calcDoubleAgentRewardSumm,
sumFieldName: 'tbxCalcDoubleAgentRewardSumm',
});
await validateRewardSum({
agentid: calcBroker,
conditionId: calcBrokerRewardCondition,
sum: calcBrokerRewardSum,
sumFieldName: 'tbxCalcBrokerRewardSum',
});
await validateRewardSum({
agentid: calcFinDepartment,
conditionId: finDepartmentRewardCondtion,
sum: finDepartmentRewardSumm,
sumFieldName: 'tbxFinDepartmentRewardSumm',
});
});
}

View File

@ -13,3 +13,5 @@ export type ProcessContext = {
export type Process = {
reactions: Record<string, (context: ProcessContext) => void>;
};
export type ValidationContext = Omit<ProcessContext, 'store'>;

View File

@ -1,8 +1,11 @@
/* eslint-disable @typescript-eslint/naming-convention */
import type { ProcessContext } from '../types';
import { createValidationSchema } from './validation';
import type { Elements } from '@/Components/Calculation/config/map/values';
import * as CRMTypes from '@/graphql/crm.types';
import ValidationHelper from '@/stores/validation/helper';
import { reaction } from 'mobx';
import { uid } from 'radash';
export function common({ store, apolloClient }: ProcessContext) {
const { $calculation } = store;
@ -150,41 +153,6 @@ export function common({ store, apolloClient }: ProcessContext) {
}
);
/**
* Добавить реакцию на изменение Типа ПЛ selectLeaseObjectType , ПЛ БУ cbxLeaseObjectUsed и Моточасы tbxEngineHours:
* Если ПЛ БУ cbxLeaseObjectUsed = True и Тип ПЛ selectLeaseObjectType = Спецтехника (id=9) и Моточасы = 0,
* то поле Моточасы tbxEngineHours должно обводиться красной рамкой и выводиться сообщение
* "Укажите Моточасы, иначе красная рамка снимается.
* При красной рамке в данном поле нельзя осуществить расчет графика.
*/
{
const validationHelper = new ValidationHelper();
reaction(
() => $calculation.$values.getValues(['leaseObjectUsed', 'engineHours', 'leaseObjectType']),
async ({ leaseObjectType: leaseObjectTypeId, leaseObjectUsed, engineHours }) => {
if (!leaseObjectTypeId) {
validationHelper.removeErrors();
return;
}
const {
data: { evo_leasingobject_type },
} = await apolloClient.query({
query: CRMTypes.GetLeaseObjectTypeDocument,
variables: { leaseObjectTypeId },
});
$calculation.element('tbxEngineHours').validate({
helper: validationHelper,
invalid: leaseObjectUsed && evo_leasingobject_type?.evo_id === '9' && !engineHours,
message: 'Не заполнено поле',
});
}
);
}
reaction(
() => $calculation.element('cbxLeaseObjectUsed').getValue(),
(leaseObjectUsed) => {
@ -196,16 +164,6 @@ export function common({ store, apolloClient }: ProcessContext) {
}
);
reaction(
() => $calculation.$values.getValues(['mileage', 'leaseObjectUsed']),
({ mileage, leaseObjectUsed }) => {
$calculation.element('tbxMileage').validate({
invalid: leaseObjectUsed && !mileage,
message: 'Не заполнено поле',
});
}
);
reaction(
() => $calculation.element('cbxLeaseObjectUsed').getValue(),
(leaseObjectUsed) => {
@ -273,38 +231,6 @@ export function common({ store, apolloClient }: ProcessContext) {
}
);
/**
* Если "Категория" содержит данные, то должны быть доступными для набора только арабские цифры и буквы латинского алфавита за исключением I, O, Q, так как они сходны по начертанию с цифрами 1, 0, 9. Можно использовать регулярное выражение: "^[A-HJ-NPR-Za-hj-npr-z0-9]{17}$".
* Иначе (если Категория = пусто) то требуется аналогичная первому условию маска, но без проверки 17ти символов (допускать и больше и меньше символов, мб так: "^[A-HJ-NPR-Za-hj-npr-z0-9]{99}$".
* Вот так: /^[A-HJ-NPR-Za-hj-npr-z0-9]+$/
*/
{
const vinRegex = /^[\dA-HJ-NPR-Za-hj-npr-z]+$/u;
const validationHelper = new ValidationHelper();
reaction(
() => $calculation.$values.getValues(['vin', 'leaseObjectCategory', 'leaseObjectUsed']),
({ vin, leaseObjectCategory }) => {
if (!vin) {
validationHelper.removeErrors();
return;
}
let invalid = vinRegex.test(vin) === false;
if (leaseObjectCategory && vin?.length !== 17) {
invalid = true;
}
$calculation.element('tbxVIN').validate({
helper: validationHelper,
invalid,
message: 'Неверно заполнено поле',
});
}
);
}
reaction(
() => $calculation.element('cbxLeaseObjectUsed').getValue(),
(leaseObjectUsed) => {
@ -316,3 +242,40 @@ export function common({ store, apolloClient }: ProcessContext) {
}
);
}
const key = uid(7);
export function validation(context: ProcessContext) {
const { store } = context;
const { $calculation } = store;
const validationSchema = createValidationSchema(context);
const helper = new ValidationHelper();
reaction(
() =>
$calculation.$values.getValues([
'engineHours',
'leaseObjectCategory',
'leaseObjectType',
'leaseObjectUsed',
'mileage',
'vin',
]),
async (values) => {
helper.removeErrors();
const validationResult = await validationSchema.safeParseAsync(values);
if (!validationResult.success) {
validationResult.error.errors.forEach(({ path, message }) => {
(path as Elements[]).forEach((elementName) => {
const removeError = $calculation.element(elementName).setError({ key, message });
if (removeError) helper.add(removeError);
});
});
}
},
{
delay: 100,
}
);
}

View File

@ -0,0 +1,81 @@
/* eslint-disable @typescript-eslint/naming-convention */
import type { ValidationContext } from '../types';
import ValuesSchema from '@/config/schema/values';
import * as CRMTypes from '@/graphql/crm.types';
import { z } from 'zod';
const vinRegex = /^[\dA-HJ-NPR-Za-hj-npr-z]+$/u;
export function createValidationSchema({ apolloClient }: ValidationContext) {
return ValuesSchema.pick({
engineHours: true,
leaseObjectCategory: true,
leaseObjectType: true,
leaseObjectUsed: true,
mileage: true,
vin: true,
}).superRefine(
async (
{
engineHours,
leaseObjectCategory,
leaseObjectType: leaseObjectTypeId,
leaseObjectUsed,
mileage,
vin,
},
ctx
) => {
/**
* Добавить реакцию на изменение Типа ПЛ selectLeaseObjectType , ПЛ БУ cbxLeaseObjectUsed и Моточасы tbxEngineHours:
* Если ПЛ БУ cbxLeaseObjectUsed = True и Тип ПЛ selectLeaseObjectType = Спецтехника (id=9) и Моточасы = 0,
* то поле Моточасы tbxEngineHours должно обводиться красной рамкой и выводиться сообщение
* "Укажите Моточасы, иначе красная рамка снимается.
* При красной рамке в данном поле нельзя осуществить расчет графика.
*/
if (leaseObjectTypeId) {
const {
data: { evo_leasingobject_type },
} = await apolloClient.query({
query: CRMTypes.GetLeaseObjectTypeDocument,
variables: { leaseObjectTypeId },
});
if (leaseObjectUsed && evo_leasingobject_type?.evo_id === '9' && !engineHours) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Не заполнено поле',
path: ['tbxEngineHours'],
});
}
}
if (leaseObjectUsed && !mileage) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Не заполнено поле',
path: ['tbxMileage'],
});
}
/**
* Если "Категория" содержит данные, то должны быть доступными для набора только арабские цифры и буквы латинского алфавита за исключением I, O, Q, так как они сходны по начертанию с цифрами 1, 0, 9. Можно использовать регулярное выражение: "^[A-HJ-NPR-Za-hj-npr-z0-9]{17}$".
* Иначе (если Категория = пусто) то требуется аналогичная первому условию маска, но без проверки 17ти символов (допускать и больше и меньше символов, мб так: "^[A-HJ-NPR-Za-hj-npr-z0-9]{99}$".
* Вот так: /^[A-HJ-NPR-Za-hj-npr-z0-9]+$/
*/
if (vin) {
let invalid = vinRegex.test(vin) === false;
if (leaseObjectCategory && vin?.length !== 17) {
invalid = true;
}
if (invalid) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Неверно заполнено поле',
path: ['tbxVIN'],
});
}
}
}
);
}

View File

@ -1,4 +1,4 @@
import type { RemoveError, ValidationParams } from '../validation/types';
import type { ValidationParams } from '../validation/types';
import OptionsStore from './options';
import StatusStore from './statuses';
import ValuesStore from './values';
@ -56,6 +56,10 @@ export default class CalculationStore {
return this.$values.getValue(valueName) as Values.ElementsTypes[E];
},
removeError: (params: Pick<ValidationParams, 'key'>) => {
this.$validation[elementName]?.removeError(params);
},
reset: () => {
const valueName = getValueName(elementName);
this.$values.resetValue(valueName);
@ -80,6 +84,12 @@ export default class CalculationStore {
return this.element(elementName);
},
setError: (params: ValidationParams) => {
if (!this.$validation[elementName]) this.createElementValidation(elementName);
return this.$validation[elementName]?.setError(params);
},
setOptions: (options: Array<BaseOption<Values.ElementsTypes[E]>>) => {
this.$options.setOptions(elementName, options);
@ -98,20 +108,5 @@ export default class CalculationStore {
return this.element(elementName);
},
validate: ({ invalid, message, silent, helper }: ValidationParams) => {
if (!this.$validation[elementName]) this.createElementValidation(elementName);
let removeError: RemoveError | undefined;
if (invalid) {
removeError = this.$validation[elementName]?.addError(message, silent);
if (helper && removeError) helper.add(removeError);
} else {
this.$validation[elementName]?.removeError(message);
}
return removeError;
},
});
}

View File

@ -2,9 +2,8 @@ import { useStore } from '@/stores/hooks';
export function useValidation(elementName) {
const { $calculation } = useStore();
const messages = $calculation.$validation[elementName]?.getMessages();
if (messages?.length) {
const hasErrors = $calculation.$validation?.[elementName]?.hasErrors;
if (hasErrors) {
return {
help: 'Некорректные данные',
isValid: false,

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 * as Insurance from '@/Components/Calculation/Form/Insurance/InsuranceTable/types';
import * as insuranceTableConfig from '@/config/tables/insurance-table';
import type RootStore from '@/stores/root';
@ -40,17 +40,10 @@ export default class InsuranceTable {
if (initialStatuses) this.statuses = initialStatuses;
};
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 = () => {

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 = () => {

View File

@ -1,32 +1,38 @@
import type { RemoveError, ValidationConfig } from './types';
import type { RemoveError, ValidationConfig, ValidationError, ValidationParams } from './types';
import { makeAutoObservable } from 'mobx';
import notification from 'ui/elements/notification';
export default class Validation {
private params: ValidationConfig;
private messages: Set<string>;
private errors: Set<ValidationError>;
constructor(config: ValidationConfig) {
this.params = config;
this.messages = new Set();
this.errors = new Set();
makeAutoObservable(this);
}
public get hasErrors() {
return this.messages.size > 0;
return this.errors.size > 0;
}
public getMessages() {
return [...this.messages];
public getErrors() {
return [...this.errors];
}
public removeError = (message: string) => {
this.messages.delete(message);
if (this.messages.size === 0) notification.close(this.params.err_key);
public removeError = ({ key }: Pick<ValidationError, 'key'>) => {
const error = [...this.errors].find((x) => x.key === key);
if (error) this.errors.delete(error);
if (this.errors.size === 0) notification.close(this.params.err_key);
};
public addError = (message: string, silent?: boolean) => {
if (!silent && !this.messages.has(message)) {
public setError = ({ key, message, silent }: ValidationParams) => {
const error = [...this.errors].find((x) => x.key === key);
if (error) this.removeError({ key });
this.errors.add({ key, message });
if (!silent) {
notification.error({
description: message,
key: this.params.err_key,
@ -34,13 +40,11 @@ export default class Validation {
});
}
this.messages.add(message);
return (() => this.removeError(message)) as RemoveError;
return (() => this.removeError({ key })) as RemoveError;
};
public clearErrors = () => {
this.messages.clear();
this.errors.clear();
notification.close(this.params.err_key);
};
}

View File

@ -1,15 +1,10 @@
import type ValidationHelper from './helper';
export type ValidationConfig = {
err_key: string;
err_title: string;
};
export type ValidationParams = {
helper?: ValidationHelper;
invalid: boolean;
message: string;
silent?: boolean;
};
export type ValidationError = { key: string; message: string };
export type ValidationParams = ValidationError & { silent?: boolean };
export type RemoveError = () => void;