diff --git a/apps/web/Components/Calculation/Form/Insurance/FinGAPTable/index.jsx b/apps/web/Components/Calculation/Form/Insurance/FinGAPTable/index.jsx
index 5ce9cb8..3547673 100644
--- a/apps/web/Components/Calculation/Form/Insurance/FinGAPTable/index.jsx
+++ b/apps/web/Components/Calculation/Form/Insurance/FinGAPTable/index.jsx
@@ -14,10 +14,10 @@ const Grid = styled(Flex)`
const Validation = observer(() => {
const store = useStore();
- const messages = store.$tables.fingap.validation.getMessages();
+ const errors = store.$tables.fingap.validation.getErrors();
- if (messages?.length) {
- return ;
+ if (errors?.length) {
+ return ;
}
return null;
diff --git a/apps/web/Components/Calculation/Form/Insurance/InsuranceTable/index.jsx b/apps/web/Components/Calculation/Form/Insurance/InsuranceTable/index.jsx
index 6cc6fde..24ef8af 100644
--- a/apps/web/Components/Calculation/Form/Insurance/InsuranceTable/index.jsx
+++ b/apps/web/Components/Calculation/Form/Insurance/InsuranceTable/index.jsx
@@ -19,10 +19,10 @@ const TableWrapper = styled.div`
const Validation = observer(() => {
const store = useStore();
- const messages = store.$tables.insurance.validation.getMessages();
+ const errors = store.$tables.insurance.validation.getErrors();
- if (messages?.length) {
- return ;
+ if (errors?.length) {
+ return ;
}
return null;
diff --git a/apps/web/Components/Calculation/Form/Payments/PaymentsTable/index.jsx b/apps/web/Components/Calculation/Form/Payments/PaymentsTable/index.jsx
index 804ecbd..a8fd83b 100644
--- a/apps/web/Components/Calculation/Form/Payments/PaymentsTable/index.jsx
+++ b/apps/web/Components/Calculation/Form/Payments/PaymentsTable/index.jsx
@@ -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 ;
+ if (errors?.length) {
+ return ;
}
return null;
diff --git a/apps/web/Components/Output/Validation.jsx b/apps/web/Components/Output/Validation.jsx
index 6435e2b..c66cff2 100644
--- a/apps/web/Components/Output/Validation.jsx
+++ b/apps/web/Components/Output/Validation.jsx
@@ -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 }) => (
-
+
));
});
@@ -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) => );
+ return errors.map(({ key, 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) => );
+ return errors.map(({ key, message }) => (
+
+ ));
}
const Errors = observer(() => {
diff --git a/apps/web/constants/values.js b/apps/web/constants/values.js
index ae2d4ca..979db7d 100644
--- a/apps/web/constants/values.js
+++ b/apps/web/constants/values.js
@@ -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;
diff --git a/apps/web/process/add-product/reactions.ts b/apps/web/process/add-product/reactions.ts
index 17dbd24..6930279 100644
--- a/apps/web/process/add-product/reactions.ts
+++ b/apps/web/process/add-product/reactions.ts
@@ -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);
diff --git a/apps/web/process/bonuses/reactions/lib/helper.ts b/apps/web/process/bonuses/lib/helper.ts
similarity index 92%
rename from apps/web/process/bonuses/reactions/lib/helper.ts
rename to apps/web/process/bonuses/lib/helper.ts
index 4b4ae16..8273620 100644
--- a/apps/web/process/bonuses/reactions/lib/helper.ts
+++ b/apps/web/process/bonuses/lib/helper.ts
@@ -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) {
diff --git a/apps/web/process/bonuses/reactions/common.ts b/apps/web/process/bonuses/reactions/common.ts
index 93a4fdc..27160b8 100644
--- a/apps/web/process/bonuses/reactions/common.ts
+++ b/apps/web/process/bonuses/reactions/common.ts
@@ -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);
diff --git a/apps/web/process/bonuses/reactions/validation.ts b/apps/web/process/bonuses/reactions/validation.ts
index 961c169..d8ff572 100644
--- a/apps/web/process/bonuses/reactions/validation.ts
+++ b/apps/web/process/bonuses/reactions/validation.ts
@@ -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,
}
);
}
diff --git a/apps/web/process/bonuses/validation.ts b/apps/web/process/bonuses/validation.ts
new file mode 100644
index 0000000..6727bda
--- /dev/null
+++ b/apps/web/process/bonuses/validation.ts
@@ -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'],
+ });
+ }
+ }
+ );
+}
diff --git a/apps/web/process/calculate/reactions/validation.ts b/apps/web/process/calculate/reactions/validation.ts
index 1ace7fb..18c95ae 100644
--- a/apps/web/process/calculate/reactions/validation.ts
+++ b/apps/web/process/calculate/reactions/validation.ts
@@ -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>) {
@@ -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,
diff --git a/apps/web/process/configurator/reactions/validation.ts b/apps/web/process/configurator/reactions/validation.ts
index 9e24896..86a8dda 100644
--- a/apps/web/process/configurator/reactions/validation.ts
+++ b/apps/web/process/configurator/reactions/validation.ts
@@ -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,
}
);
}
diff --git a/apps/web/process/configurator/validation.ts b/apps/web/process/configurator/validation.ts
new file mode 100644
index 0000000..c25abc4
--- /dev/null
+++ b/apps/web/process/configurator/validation.ts
@@ -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'],
+ });
+ }
+ }
+ }
+ );
+}
diff --git a/apps/web/process/fingap/reactions/validation.ts b/apps/web/process/fingap/reactions/validation.ts
index 3f8df5f..580208e 100644
--- a/apps/web/process/fingap/reactions/validation.ts
+++ b/apps/web/process/fingap/reactions/validation.ts
@@ -1,9 +1,14 @@
import type { ProcessContext } from '@/process/types';
+import ValidationHelper from '@/stores/validation/helper';
import { reaction } from 'mobx';
+import { uid } from 'radash';
+
+const key = uid(7);
export default function reactions({ store }: ProcessContext) {
const { $tables } = store;
+ const helper = new ValidationHelper();
reaction(
() => {
const hasPaymentsErrors = $tables.payments.validation.hasErrors;
@@ -15,10 +20,15 @@ export default function reactions({ store }: ProcessContext) {
};
},
({ hasPaymentsErrors, finGAPInsuranceCompany }) => {
- $tables.fingap.validate({
- invalid: finGAPInsuranceCompany !== null && hasPaymentsErrors,
- message: 'Неверно заполнены платежи',
- });
+ if (finGAPInsuranceCompany !== null && hasPaymentsErrors) {
+ const removeError = $tables.fingap.setError({
+ key,
+ message: 'Неверно заполнены платежи',
+ });
+ helper.add(removeError);
+ } else {
+ helper.removeErrors();
+ }
if (hasPaymentsErrors) {
$tables.fingap.clear();
diff --git a/apps/web/process/gibdd/reactions.ts b/apps/web/process/gibdd/reactions.ts
index b9326c0..a2f65ed 100644
--- a/apps/web/process/gibdd/reactions.ts
+++ b/apps/web/process/gibdd/reactions.ts
@@ -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: 'Страхование НСИБ обязательно для мотоциклистов',
- });
- }
- );
- }
}
diff --git a/apps/web/process/gibdd/validation.ts b/apps/web/process/gibdd/validation.ts
new file mode 100644
index 0000000..e1c90c6
--- /dev/null
+++ b/apps/web/process/gibdd/validation.ts
@@ -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'],
+ });
+ }
+ }
+ );
+}
diff --git a/apps/web/process/insurance/reactions.ts b/apps/web/process/insurance/reactions.ts
index 7f4a971..7acfad4 100644
--- a/apps/web/process/insurance/reactions.ts
+++ b/apps/web/process/insurance/reactions.ts
@@ -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).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,
+ }
+ );
}
diff --git a/apps/web/process/insurance/validation.ts b/apps/web/process/insurance/validation.ts
new file mode 100644
index 0000000..61f59ac
--- /dev/null
+++ b/apps/web/process/insurance/validation.ts
@@ -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'],
+ });
+ }
+ });
+ });
+}
diff --git a/apps/web/process/leasing-object/reactions/common.ts b/apps/web/process/leasing-object/reactions/common.ts
index 7e0ec09..0842641 100644
--- a/apps/web/process/leasing-object/reactions/common.ts
+++ b/apps/web/process/leasing-object/reactions/common.ts
@@ -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();
+ }
+ }
+ );
}
diff --git a/apps/web/process/leasing-object/reactions/validation.ts b/apps/web/process/leasing-object/reactions/validation.ts
index a981034..417ff38 100644
--- a/apps/web/process/leasing-object/reactions/validation.ts
+++ b/apps/web/process/leasing-object/reactions/validation.ts
@@ -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: 'Не заполнено поле',
- });
- }
- );
- }
}
diff --git a/apps/web/process/leasing-object/validation.ts b/apps/web/process/leasing-object/validation.ts
new file mode 100644
index 0000000..72d4437
--- /dev/null
+++ b/apps/web/process/leasing-object/validation.ts
@@ -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'],
+ });
+ }
+ }
+ }
+ );
+}
diff --git a/apps/web/process/payments/lib/validation.ts b/apps/web/process/payments/lib/validation.ts
deleted file mode 100644
index ae38795..0000000
--- a/apps/web/process/payments/lib/validation.ts
+++ /dev/null
@@ -1,138 +0,0 @@
-import { SEASONS_PERIOD_NUMBER, SEASONS_PERIODS } from './seasons-constants';
-import { MIN_PAYMENT } from '@/constants/values';
-import type RootStore from '@/stores/root';
-import { counting, max, min, shift, sort } from 'radash';
-import { areEqual, isSorted } from 'tools/array';
-
-// eslint-disable-next-line sonarjs/cognitive-complexity
-export default function validatePaymentsTable({ $calculation, $tables }: RootStore) {
- /**
- * в таблице платежей в столбце Соотношение платежей
- * для строк с 2 до "Срок лизинга-1" минимальное значение должно быть равно 3
- */
- {
- const leasingPeriod = $calculation.element('tbxLeasingPeriod').getValue();
- const targetPayments = $tables.payments.values.slice(1, leasingPeriod - 1);
-
- if (!targetPayments.every((payment) => payment >= MIN_PAYMENT)) {
- return `Минимальное значение платежа должно быть равно ${MIN_PAYMENT}`;
- }
- }
-
- switch ($calculation.element('radioGraphType').getValue()) {
- // Дегрессия
- case 100_000_001: {
- if (!$calculation.element('selectSeasonType').getValue()) {
- return 'Не выбран тип дегрессии';
- }
-
- /**
- * при Дегрессии все значения не должны быть равны друг другу
- * + что при Легком старте 2,3 и 4 платежи не должны быть равны 100
- */
- {
- const leasingPeriod = $calculation.element('tbxLeasingPeriod').getValue();
- const targetPayments = $tables.payments.values.slice(1, leasingPeriod - 1);
-
- if (new Set(targetPayments).size === 1) {
- return 'Платежи не должны быть равны друг другу';
- }
- }
-
- /**
- * Проверка на возрастание
- */
- {
- const leasingPeriod = $calculation.element('tbxLeasingPeriod').getValue();
- const targetPayments = $tables.payments.values.slice(1, leasingPeriod - 1);
-
- for (let i = 2; i < targetPayments.length - 1; i += 1) {
- if (targetPayments[i] > targetPayments[i - 1]) {
- return 'Платежи должны убывать';
- }
- }
- }
- /**
- * Если вид графика = Дегрессия И значения в "Соотношении платежей" у 2, 3 и 4 платежа отличаются друг от друга не более чем на 10
- * (т.е. берем значения в этих полях, определяем максимальное и минимальное значение и смотрим на их разницу)
- * то не осуществлять Расчет графика и выводить ошибку "Нельзя осуществить расчет - указана очень жетская дегрессия.
- * На 2-4 платежах Соотношение платежа должен отличаться не более чем на 10%",
- * иначе осуществлять расчет
- */
- {
- const targetPayments = $tables.payments.values.slice(1, 4);
-
- if ((max(targetPayments) || 0) - (min(targetPayments) || 0) > 10) {
- return 'Указана очень жесткая дегрессия. На 2-4 платежах Соотношение платежа должен отличаться не более чем на 10%';
- }
- }
-
- /**
- * Если вид графика = Дегрессия И значения в "Соотношении платежей" для строк с 2 До "Срок лизинга-1" как минимум 2 раза по 2 платежа должны между собой быть равны
- * (т.е. берем значения "Соотношения платежей" для строк с 2 до "Срок лизинга-1" и делаем сводную таблицу - если кол-во одинаковых значение больше 2 встречаются 2 и более раза),
- * то осуществлять расчет,
- * иначе не осуществлять Расчет графика и выводить ошибку "Нельзя осуществить расчет - указана очень жетская дегрессия. Не менее чем у 4х платежей "Соотношение платежа" должно не отличаться между самой",
- */
- {
- const leasingPeriod = $calculation.element('tbxLeasingPeriod').getValue();
- const targetPayments = $tables.payments.values.slice(1, leasingPeriod - 1);
- const counts = counting(targetPayments, (v) => v);
- if (Object.values(counts).filter((count) => count > 1).length < 2) {
- return 'Указана очень жесткая дегрессия. Не менее чем у 4х платежей соотношение должно не отличаться между собой';
- }
- }
-
- break;
- }
-
- case 100_000_003: {
- const leasingPeriod = $calculation.element('tbxLeasingPeriod').getValue();
- if (leasingPeriod < 14) {
- return 'При сезонном виде графика срок лизинга должен быть больше 14 месяцев';
- }
-
- const seasonType = $calculation.element('selectSeasonType').getValue();
- if (!seasonType) {
- return 'Не выбран тип сезонности';
- }
-
- const highSeasonStartOption = $calculation.element('selectHighSeasonStart').getOption();
- if (!highSeasonStartOption) {
- return 'Не выбрано смещение сезонности';
- }
-
- {
- const seasons = $tables.payments.values.slice(1, SEASONS_PERIOD_NUMBER + 1);
- const shiftNumber = Number.parseInt(highSeasonStartOption.label, 10) - 2;
- const unshiftedSeasons = shift(seasons, -shiftNumber);
-
- const positions = SEASONS_PERIODS[seasonType];
- const seasonsValues = positions.map((position) => unshiftedSeasons[position]);
-
- if (isSorted(seasonsValues)) {
- return 'Сезонные платежи должны убывать';
- }
- }
-
- break;
- }
-
- // Легкий старт
- case 100_000_004: {
- const targetPayments = $tables.payments.values.slice(1, 4);
- const sortedPayments = sort(targetPayments, (x) => x);
- const areEqualPayments = new Set(targetPayments).size === 1;
-
- if (!areEqual(targetPayments, sortedPayments) || areEqualPayments) {
- return '2, 3, 4 платежи должны возрастать';
- }
-
- break;
- }
- default: {
- return null;
- }
- }
-
- return null;
-}
diff --git a/apps/web/process/payments/reactions/validation.ts b/apps/web/process/payments/reactions/validation.ts
index 7d6eefb..7338325 100644
--- a/apps/web/process/payments/reactions/validation.ts
+++ b/apps/web/process/payments/reactions/validation.ts
@@ -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).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} руб. не может быть при наличии НСИБ, укажите большее значение`,
- });
- }
- );
}
diff --git a/apps/web/process/payments/validation.ts b/apps/web/process/payments/validation.ts
new file mode 100644
index 0000000..5a5fc1f
--- /dev/null
+++ b/apps/web/process/payments/validation.ts
@@ -0,0 +1,215 @@
+/* eslint-disable sonarjs/cognitive-complexity */
+/* eslint-disable complexity */
+import { SEASONS_PERIOD_NUMBER, SEASONS_PERIODS } from './lib/seasons-constants';
+import { selectHighSeasonStart } from '@/config/default-options';
+import PaymentsSchema from '@/config/schema/payments';
+import ValuesSchema from '@/config/schema/values';
+import { MIN_LASTPAYMENT_NSIB, MIN_PAYMENT } from '@/constants/values';
+import { counting, max, min, shift, sort } from 'radash';
+import { areEqual, isSorted } from 'tools/array';
+import { z } from 'zod';
+
+export function createValidationSchema() {
+ return ValuesSchema.pick({
+ graphType: true,
+ highSeasonStart: true,
+ insNSIB: true,
+ lastPaymentRub: true,
+ leasingPeriod: true,
+ seasonType: true,
+ })
+ .extend({
+ payments: PaymentsSchema,
+ })
+ .superRefine(
+ async (
+ {
+ graphType,
+ highSeasonStart,
+ leasingPeriod,
+ payments,
+ seasonType,
+ insNSIB,
+ lastPaymentRub,
+ },
+ ctx
+ ) => {
+ if (Boolean(insNSIB) && lastPaymentRub < MIN_LASTPAYMENT_NSIB) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: `Последний платеж не может быть меньше ${MIN_LASTPAYMENT_NSIB} руб. при наличии НСИБ, укажите большее значение`,
+ path: ['tbxLastPaymentRub'],
+ });
+ }
+
+ {
+ const targetPayments = payments.values.slice(1, leasingPeriod - 1);
+
+ if (!targetPayments.every((payment) => payment >= MIN_PAYMENT)) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: `Минимальное значение платежа должно быть равно ${MIN_PAYMENT}`,
+ path: ['payments'],
+ });
+ }
+ }
+
+ switch (graphType) {
+ // Дегрессия
+ case 100_000_001: {
+ if (!seasonType) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: 'Не выбран тип дегрессии',
+ path: ['payments', 'selectSeasonType'],
+ });
+ break;
+ }
+
+ /**
+ * при Дегрессии все значения не должны быть равны друг другу
+ * + что при Легком старте 2,3 и 4 платежи не должны быть равны 100
+ */
+ {
+ const targetPayments = payments.values.slice(1, leasingPeriod - 1);
+ if (new Set(targetPayments).size === 1) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: 'Платежи не должны быть равны друг другу',
+ path: ['payments'],
+ });
+ }
+ }
+
+ /**
+ * Проверка на возрастание
+ */
+ {
+ const targetPayments = payments.values.slice(1, leasingPeriod - 1);
+ for (let i = 2; i < targetPayments.length - 1; i += 1) {
+ if (targetPayments[i] > targetPayments[i - 1]) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: 'Платежи должны убывать',
+ path: ['payments'],
+ });
+ }
+ }
+ }
+
+ /**
+ * Если вид графика = Дегрессия И значения в "Соотношении платежей" у 2, 3 и 4 платежа отличаются друг от друга не более чем на 10
+ * (т.е. берем значения в этих полях, определяем максимальное и минимальное значение и смотрим на их разницу)
+ * то не осуществлять Расчет графика и выводить ошибку "Нельзя осуществить расчет - указана очень жетская дегрессия.
+ * На 2-4 платежах Соотношение платежа должен отличаться не более чем на 10%",
+ * иначе осуществлять расчет
+ */
+ {
+ const targetPayments = payments.values.slice(1, 4);
+
+ if ((max(targetPayments) || 0) - (min(targetPayments) || 0) > 10) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message:
+ 'Указана очень жесткая дегрессия. На 2-4 платежах Соотношение платежа должен отличаться не более чем на 10%',
+ path: ['payments'],
+ });
+ }
+ }
+
+ /**
+ * Если вид графика = Дегрессия И значения в "Соотношении платежей" для строк с 2 До "Срок лизинга-1" как минимум 2 раза по 2 платежа должны между собой быть равны
+ * (т.е. берем значения "Соотношения платежей" для строк с 2 до "Срок лизинга-1" и делаем сводную таблицу - если кол-во одинаковых значение больше 2 встречаются 2 и более раза),
+ * то осуществлять расчет,
+ * иначе не осуществлять Расчет графика и выводить ошибку "Нельзя осуществить расчет - указана очень жетская дегрессия. Не менее чем у 4х платежей "Соотношение платежа" должно не отличаться между самой",
+ */
+ {
+ const targetPayments = payments.values.slice(1, leasingPeriod - 1);
+ const counts = counting(targetPayments, (v) => v);
+ if (Object.values(counts).filter((count) => count > 1).length < 2) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message:
+ 'Указана очень жесткая дегрессия. Не менее чем у 4х платежей соотношение должно не отличаться между собой',
+ path: ['payments'],
+ });
+ }
+ }
+
+ break;
+ }
+
+ case 100_000_003: {
+ if (leasingPeriod < 14) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: 'При сезонном виде графика срок лизинга должен быть больше 14 месяцев',
+ path: ['payments', 'tbxLeasingPeriod'],
+ });
+ }
+
+ if (!seasonType) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: 'Не выбран тип сезонности',
+ path: ['payments', 'selectSeasonType'],
+ });
+ break;
+ }
+
+ if (!highSeasonStart) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: 'Не выбрано смещение сезонности',
+ path: ['payments', 'selectHighSeasonStart'],
+ });
+ break;
+ }
+
+ const seasons = payments.values.slice(1, SEASONS_PERIOD_NUMBER + 1);
+ const highSeasonStartOption = selectHighSeasonStart.find(
+ (x) => x.value === highSeasonStart
+ );
+
+ if (highSeasonStartOption) {
+ const shiftNumber = Number.parseInt(highSeasonStartOption.label, 10) - 2;
+ const unshiftedSeasons = shift(seasons, -shiftNumber);
+
+ const positions = SEASONS_PERIODS[seasonType];
+ const seasonsValues = positions.map((position) => unshiftedSeasons[position]);
+
+ if (isSorted(seasonsValues)) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: 'Сезонные платежи должны убывать',
+ path: ['payments'],
+ });
+ }
+ }
+
+ break;
+ }
+
+ // Легкий старт
+ case 100_000_004: {
+ const targetPayments = payments.values.slice(1, 4);
+ const sortedPayments = sort(targetPayments, (x) => x);
+ const areEqualPayments = new Set(targetPayments).size === 1;
+
+ if (!areEqual(targetPayments, sortedPayments) || areEqualPayments) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: '2, 3, 4 платежи должны возрастать',
+ path: ['payments'],
+ });
+ }
+
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+ }
+ );
+}
diff --git a/apps/web/process/price/reactions/validation.ts b/apps/web/process/price/reactions/validation.ts
index 8860c17..32343d9 100644
--- a/apps/web/process/price/reactions/validation.ts
+++ b/apps/web/process/price/reactions/validation.ts
@@ -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,
}
);
}
diff --git a/apps/web/process/price/validation.ts b/apps/web/process/price/validation.ts
new file mode 100644
index 0000000..427a09e
--- /dev/null
+++ b/apps/web/process/price/validation.ts
@@ -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'],
+ });
+ }
+ }
+ );
+}
diff --git a/apps/web/process/supplier-agent/lib/create-reactions.ts b/apps/web/process/supplier-agent/lib/create-reactions.ts
index 9022c5d..5108e68 100644
--- a/apps/web/process/supplier-agent/lib/create-reactions.ts
+++ b/apps/web/process/supplier-agent/lib/create-reactions.ts
@@ -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