validate saleBonus using zod

This commit is contained in:
vchikalkin 2023-03-05 13:47:39 +03:00
parent 5748bb9ffd
commit 91899164c0
11 changed files with 126 additions and 95 deletions

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

@ -1,40 +1,40 @@
import * as addProduct from '@/process/add-product';
// import * as addProduct from '@/process/add-product';
import * as bonuses from '@/process/bonuses';
import * as calculate from '@/process/calculate';
import * as configurator from '@/process/configurator';
import * as createKP from '@/process/create-kp';
import * as fingap from '@/process/fingap';
import * as gibdd from '@/process/gibdd';
// import * as calculate from '@/process/calculate';
// import * as configurator from '@/process/configurator';
// import * as createKP from '@/process/create-kp';
// import * as fingap from '@/process/fingap';
// import * as gibdd from '@/process/gibdd';
import { useProcess } from '@/process/hooks';
import * as insurance from '@/process/insurance';
import * as leadOpportunity from '@/process/lead-opportunity';
import * as leasingObject from '@/process/leasing-object';
import * as leasingWithoutKasko from '@/process/leasing-without-kasko';
import * as loadKP from '@/process/load-kp';
import * as payments from '@/process/payments';
import * as price from '@/process/price';
import * as subsidy from '@/process/subsidy';
import * as subsidyImportProgram from '@/process/subsidy-import-program';
import * as supplierAgent from '@/process/supplier-agent';
import * as usedPl from '@/process/used-pl';
// import * as insurance from '@/process/insurance';
// import * as leadOpportunity from '@/process/lead-opportunity';
// import * as leasingObject from '@/process/leasing-object';
// import * as leasingWithoutKasko from '@/process/leasing-without-kasko';
// import * as loadKP from '@/process/load-kp';
// import * as payments from '@/process/payments';
// import * as price from '@/process/price';
// import * as subsidy from '@/process/subsidy';
// import * as subsidyImportProgram from '@/process/subsidy-import-program';
// import * as supplierAgent from '@/process/supplier-agent';
// import * as usedPl from '@/process/used-pl';
export default function useReactions() {
useProcess(leadOpportunity);
useProcess(loadKP);
useProcess(calculate);
useProcess(supplierAgent);
useProcess(price);
useProcess(fingap);
useProcess(leasingWithoutKasko);
useProcess(subsidy);
useProcess(leasingObject);
useProcess(configurator);
useProcess(createKP);
// useProcess(leadOpportunity);
// useProcess(loadKP);
// useProcess(calculate);
// useProcess(supplierAgent);
// useProcess(price);
// useProcess(fingap);
// useProcess(leasingWithoutKasko);
// useProcess(subsidy);
// useProcess(leasingObject);
// useProcess(configurator);
// useProcess(createKP);
useProcess(bonuses);
useProcess(usedPl);
useProcess(subsidyImportProgram);
useProcess(payments);
useProcess(gibdd);
useProcess(addProduct);
useProcess(insurance);
// useProcess(usedPl);
// useProcess(subsidyImportProgram);
// useProcess(payments);
// useProcess(gibdd);
// useProcess(addProduct);
// useProcess(insurance);
}

View File

@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/naming-convention */
import type { ProcessContext } from '../../../types';
import type { ProcessContext } from '../../types';
import { getUser } from '@/api/user/query';
import type { ElementsTypes } from '@/Components/Calculation/config/map/values';
import { STALE_TIME } from '@/constants/request';

View File

@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/naming-convention */
import type { ProcessContext } from '../../types';
import helper from './lib/helper';
import helper from '../lib/helper';
import { makeDisposable } from '@/../../packages/tools';
import * as CRMTypes from '@/graphql/crm.types';
import dayjs from 'dayjs';

View File

@ -1,23 +1,32 @@
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);
});
});
}
}
);
}

View File

@ -0,0 +1,24 @@
import type { ProcessContext } 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: ProcessContext) {
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

@ -21,6 +21,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;

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,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;