trpc: add create-kp prodecure

This commit is contained in:
vchikalkin 2023-03-27 13:05:42 +03:00
parent f2fed86fc7
commit 4b2fee5e69
19 changed files with 410 additions and 183 deletions

1
.env
View File

@ -8,6 +8,7 @@ USERS_SUPER=["akalinina","vchikalkin"]
####### URLS ########
URL_GET_USER_DIRECT=
URL_CRM_GRAPHQL_DIRECT=
URL_CRM_CREATEKP_DIRECT=
URL_CORE_FINGAP_DIRECT=
URL_CORE_CALCULATE_DIRECT=
URL_1C_TRANSTAX_DIRECT=

View File

@ -13,11 +13,9 @@ export async function calculateFinGAP(payload: RequestFinGAP, { signal }: QueryF
return data;
}
export async function calculate(payload: RequestCalculate, { signal }: QueryFunctionContext) {
export async function calculate(payload: RequestCalculate): Promise<ResponseCalculate> {
return await axios
.post<ResponseCalculate>(URL_CORE_CALCULATE, payload, {
signal,
})
.post<ResponseCalculate>(URL_CORE_CALCULATE, payload)
.then((response) => response.data)
.catch((error) => error.response.data);
}

View File

@ -137,13 +137,13 @@ const AdditionalDataSchema = z.object({
export type AdditionalData = z.infer<typeof AdditionalDataSchema>;
export const CalculateRequestSchema = z.object({
export const RequestCalculateSchema = z.object({
additionalData: AdditionalDataSchema,
preparedPayments: PreparedPaymentSchema,
preparedValues: PreparedValuesSchema,
});
export type RequestCalculate = z.infer<typeof CalculateRequestSchema>;
export type RequestCalculate = z.infer<typeof RequestCalculateSchema>;
const PostValuesSchema = z.object({
baseCost: z.number(),

12
apps/web/api/crm/query.ts Normal file
View File

@ -0,0 +1,12 @@
import type { RequestCreateKP, ResponseCreateKP } from './types';
import getUrls from '@/config/urls';
import axios from 'axios';
const { URL_CRM_CREATEKP } = getUrls();
export async function createKP(payload: RequestCreateKP): Promise<ResponseCreateKP> {
return await axios
.post(URL_CRM_CREATEKP, payload)
.then((response) => ({ ...response.data, success: true }))
.catch((error) => ({ ...error.response.data, success: false }));
}

41
apps/web/api/crm/types.ts Normal file
View File

@ -0,0 +1,41 @@
import { RequestCalculateSchema, ResponseCalculateSchema } from '../core/types';
import { RiskSchema } from '@/config/schema/fingap';
import { RowSchema } from '@/config/schema/insurance';
import ValuesSchema from '@/config/schema/values';
import { z } from 'zod';
export const RequestCreateKPSchema = z.object({
calculation: z
.object({
calculationValues: ValuesSchema,
})
.and(
RequestCalculateSchema.omit({
preparedValues: true,
})
)
.and(
ResponseCalculateSchema.omit({
errors: true,
})
),
domainName: z.string(),
finGAP: RiskSchema.array(),
insurance: RowSchema.array(),
});
export type RequestCreateKP = z.infer<typeof RequestCreateKPSchema>;
export const ResponseCreateKPSchema = z.union([
z.object({
evo_quotename: z.string(),
success: z.literal(true),
}),
z.object({
fullMessage: z.string(),
message: z.string(),
success: z.literal(false),
}),
]);
export type ResponseCreateKP = z.infer<typeof ResponseCreateKPSchema>;

View File

@ -6,6 +6,7 @@ const envSchema = z.object({
URL_1C_TRANSTAX_DIRECT: z.string(),
URL_CORE_CALCULATE_DIRECT: z.string(),
URL_CORE_FINGAP_DIRECT: z.string(),
URL_CRM_CREATEKP_DIRECT: z.string(),
URL_CRM_GRAPHQL_DIRECT: z.string(),
URL_GET_USER_DIRECT: z.string(),
USE_DEV_COLORS: z.unknown().optional().transform(Boolean),

View File

@ -12,6 +12,7 @@ const serverRuntimeConfigSchema = envSchema.pick({
URL_1C_TRANSTAX_DIRECT: true,
URL_CORE_CALCULATE_DIRECT: true,
URL_CORE_FINGAP_DIRECT: true,
URL_CRM_CREATEKP_DIRECT: true,
URL_CRM_GRAPHQL_DIRECT: true,
URL_GET_USER_DIRECT: true,
});

View File

@ -15,6 +15,7 @@ function getUrls() {
URL_CORE_FINGAP_DIRECT,
URL_1C_TRANSTAX_DIRECT,
URL_CORE_CALCULATE_DIRECT,
URL_CRM_CREATEKP_DIRECT,
PORT,
} = serverRuntimeConfigSchema.parse(serverRuntimeConfig);
@ -24,6 +25,7 @@ function getUrls() {
URL_1C_TRANSTAX: URL_1C_TRANSTAX_DIRECT,
URL_CORE_CALCULATE: URL_CORE_CALCULATE_DIRECT,
URL_CORE_FINGAP: URL_CORE_FINGAP_DIRECT,
URL_CRM_CREATEKP: URL_CRM_CREATEKP_DIRECT,
URL_CRM_GRAPHQL: URL_CRM_GRAPHQL_DIRECT,
URL_GET_USER: URL_GET_USER_DIRECT,
};
@ -38,6 +40,7 @@ function getUrls() {
URL_1C_TRANSTAX: withBasePath(urls.URL_1C_TRANSTAX_PROXY),
URL_CORE_CALCULATE: withBasePath(urls.URL_CORE_CALCULATE_PROXY),
URL_CORE_FINGAP: withBasePath(urls.URL_CORE_FINGAP_PROXY),
URL_CRM_CREATEKP: withBasePath(urls.URL_CRM_CREATEKP_PROXY),
URL_CRM_GRAPHQL: withBasePath(urls.URL_CRM_GRAPHQL_PROXY),
URL_GET_USER: withBasePath(urls.URL_GET_USER_PROXY),
};

View File

@ -2,6 +2,7 @@ module.exports = {
URL_1C_TRANSTAX_PROXY: '/api/1c/transtax',
URL_CORE_CALCULATE_PROXY: '/api/core/calculate',
URL_CORE_FINGAP_PROXY: '/api/core/fingap',
URL_CRM_CREATEKP_PROXY: '/api/crm/create-kp',
URL_CRM_GRAPHQL_PROXY: '/api/graphql/crm',
URL_GET_USER_PROXY: '/api/auth/user',
};

View File

@ -1,14 +1,12 @@
import type { ProcessContext } from '../types';
import { toJS } from 'mobx';
import notification from 'ui/elements/notification';
const key = 'ACTION_CALCULATE';
const message = 'Ошибка во время расчета графика';
const errorMessage = 'Ошибка во время расчета графика!';
const successMessage = 'Расчет графика завершен успешно!';
/**
* @param {import('../types').ProcessContext} context
* @param {*} onCompleted
*/
export async function action({ store, trpcClient }) {
export async function action({ store, trpcClient }: ProcessContext) {
const { $calculation, $tables, $results } = store;
$calculation.$status.setStatus('btnCalculate', 'Loading');
@ -36,19 +34,24 @@ export async function action({ store, trpcClient }) {
notification.error({
description: res.error,
key,
message,
message: errorMessage,
});
} else {
$results.setPayments(res.data.resultPayments);
$results.setValues(res.data.resultValues);
$calculation.$values.setValues(res.data.values);
notification.success({
key,
message: successMessage,
});
}
})
.catch((error) => {
notification.error({
description: JSON.stringify(error),
key,
message,
message: errorMessage,
});
})
.finally(() => {

View File

@ -1 +1,67 @@
export function action() {}
import { updateSelectQuote } from '../lead-opportunity/reactions/common';
import type { ProcessContext } from '../types';
import { toJS } from 'mobx';
import notification from 'ui/elements/notification';
const key = 'ACTION_CREATEKP';
const errorMessage = 'Ошибка во время создания КП!';
const successMessage = 'КП создано!';
export function action({ store, trpcClient, apolloClient }: ProcessContext) {
const { $calculation, $tables, $results } = store;
$calculation.$status.setStatus('btnCalculate', 'Loading');
$calculation.$status.setStatus('btnCreateKP', 'Loading');
$results.clear();
const values = $calculation.$values.getValues();
const insurance = {
fingap: toJS($tables.insurance.row('fingap').getValues()),
kasko: toJS($tables.insurance.row('kasko').getValues()),
osago: toJS($tables.insurance.row('osago').getValues()),
};
const fingap = $tables.fingap.getSelectedRisks();
const payments = toJS($tables.payments.values);
trpcClient.createQuote
.mutate({
fingap,
insurance: { values: insurance },
payments: { values: payments },
values,
})
.then(async (res) => {
if (res.success === false) {
notification.error({
description: res.error,
key,
message: errorMessage,
});
} else {
$results.setPayments(res.data.resultPayments);
$results.setValues(res.data.resultValues);
$calculation.$values.setValues(res.data.values);
notification.success({
key,
message: successMessage,
});
await updateSelectQuote({ apolloClient, store });
}
})
.catch((error) => {
notification.error({
description: JSON.stringify(error),
key,
message: errorMessage,
});
})
.finally(() => {
$calculation.$status.setStatus('btnCalculate', 'Default');
$calculation.$status.setStatus('btnCreateKP', 'Default');
});
}

View File

@ -92,33 +92,44 @@ export default function reactions({ store, apolloClient }: ProcessContext) {
reaction(
() => $calculation.$values.getValues(['recalcWithRevision', 'lead']),
async ({ lead: leadid, recalcWithRevision }) => {
if (leadid) {
const {
data: { quotes },
} = await apolloClient.query({
fetchPolicy: 'network-only',
query: CRMTypes.GetQuotesDocument,
variables: {
leadid,
},
});
if (recalcWithRevision) {
const filteredQuotes = quotes?.filter(
(quote) =>
quote?.evo_recalc_limit &&
quote.evo_recalc_limit > 0 &&
quote.evo_statuscodeidData?.evo_id === '2.3' &&
!quote.evo_purchases_participation
);
$calculation.element('selectQuote').setOptions(normalizeOptions(filteredQuotes));
} else {
$calculation.element('selectQuote').setOptions(normalizeOptions(quotes));
}
} else {
$calculation.element('selectQuote').reset();
}
async () => {
await updateSelectQuote({ apolloClient, store });
}
);
}
export async function updateSelectQuote({
store,
apolloClient,
}: Pick<ProcessContext, 'apolloClient' | 'store'>) {
const { $calculation } = store;
const leadid = $calculation.element('selectLead').getValue();
const recalcWithRevision = $calculation.element('cbxRecalcWithRevision').getValue();
if (leadid) {
const {
data: { quotes },
} = await apolloClient.query({
fetchPolicy: 'network-only',
query: CRMTypes.GetQuotesDocument,
variables: {
leadid,
},
});
if (recalcWithRevision) {
const filteredQuotes = quotes?.filter(
(quote) =>
quote?.evo_recalc_limit &&
quote.evo_recalc_limit > 0 &&
quote.evo_statuscodeidData?.evo_id === '2.3' &&
!quote.evo_purchases_participation
);
$calculation.element('selectQuote').setOptions(normalizeOptions(filteredQuotes));
} else {
$calculation.element('selectQuote').setOptions(normalizeOptions(quotes));
}
} else {
$calculation.element('selectQuote').reset();
}
}

View File

@ -1,52 +1,3 @@
/* eslint-disable zod/require-strict */
import { RiskSchema } from '@/config/schema/fingap';
import { KeysSchema, RowSchema } from '@/config/schema/insurance';
import PaymentsSchema from '@/config/schema/payments';
import ValuesSchema from '@/config/schema/values';
import { z } from 'zod';
const { quote, recalcWithRevision, lead, opportunity } = ValuesSchema.shape;
export const GetQuoteInputDataSchema = z
.object({
values: z
.object({
lead,
opportunity,
quote: quote.unwrap(),
recalcWithRevision,
})
.required(),
})
.strict();
export type GetQuoteInputData = z.infer<typeof GetQuoteInputDataSchema>;
const FinGAPSchema = z.object({
keys: z.array(RiskSchema.shape.key),
});
const InsuranceSchema = z.object({
values: z.record(KeysSchema, RowSchema),
});
export const GetQuoteOutputDataSchema = z
.object({
fingap: FinGAPSchema,
insurance: InsuranceSchema,
payments: PaymentsSchema,
values: ValuesSchema,
})
.strict();
export type GetQuoteOutputData = z.infer<typeof GetQuoteOutputDataSchema>;
export const GetQuoteProcessDataSchema = GetQuoteOutputDataSchema.omit({
values: true,
})
.extend({
values: ValuesSchema.partial(),
})
.partial();
export type GetQuoteProcessData = z.infer<typeof GetQuoteProcessDataSchema>;
export type { GetQuoteInputData } from '@/server/routers/quote/types';
export type { GetQuoteOutputData } from '@/server/routers/quote/types';
export type { GetQuoteProcessData } from '@/server/routers/quote/types';

View File

@ -5,9 +5,7 @@ import { validate } from './lib/validation';
import { CalculateInputSchema, CalculateOutputSchema } from './types';
import { calculate } from '@/api/core/query';
import initializeApollo from '@/apollo/client';
import { STALE_TIME } from '@/constants/request';
import { protectedProcedure } from '@/server/procedure';
import type { QueryFunctionContext } from '@tanstack/react-query';
import { QueryClient } from '@tanstack/react-query';
export const calculateRouter = router({
@ -44,13 +42,7 @@ export const calculateRouter = router({
user: ctx.user,
});
const calculateResult = await queryClient.fetchQuery(
['calculate'],
(context: QueryFunctionContext) => calculate(requestData, context),
{
staleTime: STALE_TIME,
}
);
const calculateResult = await calculate(requestData);
if (calculateResult.errors?.length > 0) {
return {

View File

@ -29,16 +29,15 @@ export const OutputDataSchema = z.object({
export type OutputData = z.infer<typeof OutputDataSchema>;
export const CalculateOutputSchema = z
.object({
export const CalculateOutputSchema = z.union([
z.object({
data: OutputDataSchema,
success: z.boolean(),
})
.or(
z.object({
error: z.string(),
success: z.boolean(),
})
);
success: z.literal(true),
}),
z.object({
error: z.string(),
success: z.literal(false),
}),
]);
export type CalculateOutput = z.infer<typeof CalculateOutputSchema>;

View File

@ -1,69 +0,0 @@
/* eslint-disable canonical/sort-keys */
import { publicProcedure } from '../procedure';
import { router } from '../trpc';
import defaultValues from '@/config/default-values';
import * as insuranceTable from '@/config/tables/insurance-table';
import * as addProduct from '@/process/add-product';
import * as bonuses from '@/process/bonuses';
import * as configurator from '@/process/configurator';
import * as fingapProcess from '@/process/fingap';
import * as gibdd from '@/process/gibdd';
import * as insuranceProcess from '@/process/insurance';
import * as leasingObject from '@/process/leasing-object';
import * as loadKP from '@/process/load-kp';
import * as paymentsProcess from '@/process/payments';
import * as price from '@/process/price';
import * as subsidy from '@/process/subsidy';
import * as supplierAgent from '@/process/supplier-agent';
const { GetQuoteInputDataSchema, GetQuoteOutputDataSchema } = loadKP;
const { DEFAULT_FINGAP_ROW, DEFAULT_KASKO_ROW, DEFAULT_OSAGO_ROW } = insuranceTable;
const defaultInsurance = {
values: {
fingap: DEFAULT_FINGAP_ROW,
kasko: DEFAULT_KASKO_ROW,
osago: DEFAULT_OSAGO_ROW,
},
};
const defaultFingap = { keys: [] };
const defaultPayments = { values: [] };
export const quoteRouter = router({
getQuote: publicProcedure
.input(GetQuoteInputDataSchema)
.output(GetQuoteOutputDataSchema)
.query(async ({ input }) => {
const processData = await Promise.all(
[
configurator,
supplierAgent,
paymentsProcess,
price,
bonuses,
leasingObject,
fingapProcess,
gibdd,
subsidy,
insuranceProcess,
addProduct,
].map(({ getKPData }) => getKPData(input))
);
const values = processData.reduce(
(obj, data) => Object.assign(obj, data.values),
defaultValues
);
const payments = processData.find((x) => x.payments)?.payments ?? defaultPayments;
const insurance = processData.find((x) => x.insurance)?.insurance ?? defaultInsurance;
const fingap = processData.find((x) => x.fingap)?.fingap ?? defaultFingap;
return {
values,
payments,
insurance,
fingap,
};
}),
});

View File

@ -0,0 +1,153 @@
/* eslint-disable canonical/sort-keys */
import { protectedProcedure, publicProcedure } from '../../procedure';
import { router } from '../../trpc';
import { createRequestData } from '../calculate/lib/request';
import { transformCalculateResults } from '../calculate/lib/transform';
import { validate } from '../calculate/lib/validation';
import {
CreateQuoteInputDataSchema,
CreateQuoteOutputDataSchema,
GetQuoteInputDataSchema,
GetQuoteOutputDataSchema,
} from './types';
import { calculate } from '@/api/core/query';
import { createKP } from '@/api/crm/query';
import initializeApollo from '@/apollo/client';
import defaultValues from '@/config/default-values';
import * as insuranceTable from '@/config/tables/insurance-table';
import * as addProduct from '@/process/add-product';
import * as bonuses from '@/process/bonuses';
import * as configurator from '@/process/configurator';
import * as fingapProcess from '@/process/fingap';
import * as gibdd from '@/process/gibdd';
import * as insuranceProcess from '@/process/insurance';
import * as leasingObject from '@/process/leasing-object';
import * as paymentsProcess from '@/process/payments';
import * as price from '@/process/price';
import * as subsidy from '@/process/subsidy';
import * as supplierAgent from '@/process/supplier-agent';
import { QueryClient } from '@tanstack/react-query';
const { DEFAULT_FINGAP_ROW, DEFAULT_KASKO_ROW, DEFAULT_OSAGO_ROW } = insuranceTable;
const defaultInsurance = {
values: {
fingap: DEFAULT_FINGAP_ROW,
kasko: DEFAULT_KASKO_ROW,
osago: DEFAULT_OSAGO_ROW,
},
};
const defaultFingap = { keys: [] };
const defaultPayments = { values: [] };
export const quoteRouter = router({
getQuote: publicProcedure
.input(GetQuoteInputDataSchema)
.output(GetQuoteOutputDataSchema)
.query(async ({ input }) => {
const processData = await Promise.all(
[
configurator,
supplierAgent,
paymentsProcess,
price,
bonuses,
leasingObject,
fingapProcess,
gibdd,
subsidy,
insuranceProcess,
addProduct,
].map(({ getKPData }) => getKPData(input))
);
const values = processData.reduce(
(obj, data) => Object.assign(obj, data.values),
defaultValues
);
const payments = processData.find((x) => x.payments)?.payments ?? defaultPayments;
const insurance = processData.find((x) => x.insurance)?.insurance ?? defaultInsurance;
const fingap = processData.find((x) => x.fingap)?.fingap ?? defaultFingap;
return {
values,
payments,
insurance,
fingap,
};
}),
createQuote: protectedProcedure
.input(CreateQuoteInputDataSchema)
.output(CreateQuoteOutputDataSchema)
.mutation(async ({ input, ctx }) => {
const apolloClient = initializeApollo();
const queryClient = new QueryClient();
const validationResult = await validate({
context: {
apolloClient,
queryClient,
user: ctx.user,
},
input,
});
if (validationResult.success === false) {
return {
error: validationResult.error,
success: false,
};
}
const requestData = await createRequestData({
context: {
apolloClient,
queryClient,
user: ctx.user,
},
input,
user: ctx.user,
});
const calculateResult = await calculate(requestData);
if (calculateResult.errors?.length > 0) {
return {
error: calculateResult.errors[0],
success: false,
};
}
const createKPResult = await createKP({
domainName: ctx.user.domainName,
finGAP: input.fingap,
insurance: Object.values(input.insurance.values),
calculation: {
calculationValues: input.values,
...calculateResult,
preparedPayments: requestData.preparedPayments,
additionalData: requestData.additionalData,
},
});
if (createKPResult.success === false) {
return {
success: false,
error: createKPResult.message || createKPResult.fullMessage,
};
}
const result = transformCalculateResults({
calculateInput: input,
requestCalculate: requestData,
responseCalculate: calculateResult,
});
return {
data: result,
success: true,
};
}),
});

View File

@ -0,0 +1,59 @@
/* eslint-disable zod/require-strict */
import { CalculateInputSchema } from '../calculate/types';
import { RiskSchema } from '@/config/schema/fingap';
import { KeysSchema, RowSchema } from '@/config/schema/insurance';
import PaymentsSchema from '@/config/schema/payments';
import ValuesSchema from '@/config/schema/values';
import { z } from 'zod';
const { quote, recalcWithRevision, lead, opportunity } = ValuesSchema.shape;
export const GetQuoteInputDataSchema = z
.object({
values: z
.object({
lead,
opportunity,
quote: quote.unwrap(),
recalcWithRevision,
})
.required(),
})
.strict();
export type GetQuoteInputData = z.infer<typeof GetQuoteInputDataSchema>;
const FinGAPSchema = z.object({
keys: z.array(RiskSchema.shape.key),
});
const InsuranceSchema = z.object({
values: z.record(KeysSchema, RowSchema),
});
export const GetQuoteOutputDataSchema = z
.object({
fingap: FinGAPSchema,
insurance: InsuranceSchema,
payments: PaymentsSchema,
values: ValuesSchema,
})
.strict();
export type GetQuoteOutputData = z.infer<typeof GetQuoteOutputDataSchema>;
export const GetQuoteProcessDataSchema = GetQuoteOutputDataSchema.omit({
values: true,
})
.extend({
values: ValuesSchema.partial(),
})
.partial();
export type GetQuoteProcessData = z.infer<typeof GetQuoteProcessDataSchema>;
export const CreateQuoteInputDataSchema = CalculateInputSchema.extend({
fingap: RiskSchema.array(),
});
export { CalculateOutputSchema as CreateQuoteOutputDataSchema } from '../calculate/types';

View File

@ -3,7 +3,7 @@ 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';
import { makeAutoObservable, observable } from 'mobx';
import { makeAutoObservable, observable, toJS } from 'mobx';
export default class FinGAPTable {
private root: RootStore;
@ -24,6 +24,10 @@ export default class FinGAPTable {
this.root = rootStore;
}
public getSelectedRisks() {
return toJS(this.risks.filter((x) => this.selectedKeys.has(x.key)));
}
public setRisks = (risks: FinGAP.Risk[]) => {
this.risks.replace(risks);
};