calculate pt.3: it's calculate!!!

This commit is contained in:
vchikalkin 2023-03-22 21:09:43 +03:00
parent f45ab7f3a9
commit dbe6bdfe16
25 changed files with 748 additions and 310 deletions

1
.env
View File

@ -9,6 +9,7 @@ USERS_SUPER=["akalinina","vchikalkin"]
URL_GET_USER_DIRECT=
URL_CRM_GRAPHQL_DIRECT=
URL_CORE_FINGAP_DIRECT=
URL_CORE_CALCULATE_DIRECT=
URL_1C_TRANSTAX_DIRECT=
####### SERVER ########

View File

@ -1,8 +1,8 @@
/* eslint-disable canonical/sort-keys */
import type { Payment } from './types';
import type { ResultPayment } from '@/stores/results/types';
import type { ColumnsType } from 'antd/lib/table';
export const columns: ColumnsType<Payment> = [
export const columns: ColumnsType<ResultPayment> = [
{
key: 'num',
dataIndex: 'num',

View File

@ -1,7 +0,0 @@
export type Payment = {
key: string;
ndsCompensation: string;
num: number;
paymentSum: string;
redemptionAmount: string;
};

View File

@ -1,9 +1,10 @@
import type { Values } from '@/stores/results/types';
import type { ResultValues } from '@/stores/results/types';
import { moneyFormatter, percentFormatter } from 'tools';
export const id = 'output';
export const title = 'Результаты';
export const titles: Record<Values, string> = {
export const titles: Record<keyof ResultValues, string> = {
resultAB_FL: 'АВ ФЛ, без НДФЛ.',
resultAB_UL: 'АВ ЮЛ, с НДС.',
resultBonusDopProd: 'Бонус МПЛ за доп.продукты, без НДФЛ',
@ -18,55 +19,31 @@ export const titles: Record<Values, string> = {
resultInsKasko: 'КАСКО, НС, ДГО в графике',
resultInsOsago: 'ОСАГО в графике',
resultLastPayment: 'Последний платеж',
resultParticipationAmount: 'Сумма участия (для принятия решения)',
resultPlPrice: 'Стоимость ПЛ с НДС',
resultPriceUpPr: 'Удорожание, год',
resultTerm: 'Срок, мес.',
resultTotalGraphwithNDS: 'Итого по графику, с НДС',
};
const moneyFormatters = Object.fromEntries(
(
[
'resultTotalGraphwithNDS',
'resultPlPrice',
'resultInsKasko',
'resultInsOsago',
'resultDopProdSum',
'resultFirstPayment',
'resultLastPayment',
'resultAB_FL',
'resultAB_UL',
'resultBonusMPL',
'resultDopMPLLeasing',
'resultBonusDopProd',
'tbxSubsidySum',
'resultBonusSafeFinance',
'resultPriceUpPr',
] as Values[]
).map((a) => [
a,
Intl.NumberFormat('ru', {
currency: 'RUB',
style: 'currency',
}).format,
])
);
const percentFormatters = Object.fromEntries(
(['resultIRRGraphPerc', 'resultIRRNominalPerc', 'resultFirstPaymentRiskPolicy'] as Values[]).map(
(a) => [
a,
Intl.NumberFormat('ru', {
maximumFractionDigits: 2,
minimumFractionDigits: 2,
style: 'percent',
}).format,
]
)
);
const defaultFormatters = {
resultTerm: Intl.NumberFormat('ru').format,
export const formatters = {
resultAB_FL: moneyFormatter,
resultAB_UL: moneyFormatter,
resultBonusDopProd: moneyFormatter,
resultBonusMPL: moneyFormatter,
resultBonusSafeFinance: moneyFormatter,
resultDopMPLLeasing: moneyFormatter,
resultDopProdSum: moneyFormatter,
resultFirstPayment: moneyFormatter,
resultFirstPaymentRiskPolicy: percentFormatter,
resultIRRGraphPerc: percentFormatter,
resultIRRNominalPerc: percentFormatter,
resultInsKasko: moneyFormatter,
resultInsOsago: moneyFormatter,
resultLastPayment: moneyFormatter,
resultParticipationAmount: moneyFormatter,
resultPlPrice: moneyFormatter,
resultPriceUpPr: moneyFormatter,
resultTerm: (v) => v,
resultTotalGraphwithNDS: moneyFormatter,
};
export const formatters = Object.assign(moneyFormatters, percentFormatters, defaultFormatters);

View File

@ -1,9 +1,9 @@
import type { RequestFinGAP, ResponseFinGAP } from './types';
import type { RequestCalculate, RequestFinGAP, ResponseCalculate, ResponseFinGAP } from './types';
import getUrls from '@/config/urls';
import type { QueryFunctionContext } from '@tanstack/react-query';
import axios from 'axios';
const { URL_CORE_FINGAP } = getUrls();
const { URL_CORE_FINGAP, URL_CORE_CALCULATE } = getUrls();
export async function calculateFinGAP(payload: RequestFinGAP, { signal }: QueryFunctionContext) {
const { data } = await axios.post<ResponseFinGAP>(URL_CORE_FINGAP, payload, {
@ -12,3 +12,11 @@ export async function calculateFinGAP(payload: RequestFinGAP, { signal }: QueryF
return data;
}
export async function calculate(payload: RequestCalculate, { signal }: QueryFunctionContext) {
const { data } = await axios.post<ResponseCalculate>(URL_CORE_CALCULATE, payload, {
signal,
});
return data;
}

View File

@ -0,0 +1,425 @@
import { z } from 'zod';
export const PreparedValuesSchema = z.object({
acceptSum: z.number(),
agentsSum: z.number(),
balanceHolder: z.number(),
baseRatCost: z.number(),
baseRegistration: z.number(),
bonus: z.number(),
bonusCoefficient: z.number(),
bonusFinGAP: z.number(),
bonusFix: z.number(),
bonusNsPr: z.number(),
bonusNsibPr: z.number(),
bonusRatPr: z.number(),
brandId: z.string().nullable(),
brokerOfDeliverySum: z.number(),
brokerSum: z.number(),
calcDate: z.date(),
calcType: z.number(),
carCapacity: z.number(),
carCarrying: z.number(),
carSeats: z.number(),
cityc: z.string(),
comissionRub: z.number(),
configurationId: z.string().nullable(),
deliverySum: z.number(),
deliveryTime: z.number(),
deprecationTime: z.number(),
directorBonus: z.number(),
directorBonusFinGAP: z.number(),
directorBonusFix: z.number(),
directorBonusNsib: z.number(),
directorExtraBonus: z.number(),
discount: z.number(),
districtRate: z.number(),
doubleAgentsSum: z.number(),
extraBonus: z.number(),
financialDeptOfDeliverySum: z.number(),
firstPayment: z.number(),
firstPaymentAbs: z.number(),
firstPaymentNdsAbs: z.number(),
firstPaymentSum: z.number(),
firstPaymentWithNdsAbs: z.number(),
fuelCardSum: z.number(),
gpsCostPaymentSum: z.number(),
iRR_MSFO_Plan: z.number(),
importProgramSum: z.number(),
importerSum: z.number(),
insuranceBonus: z.number(),
insuranceBonusLoss: z.number(),
insuranceContract: z.number(),
insuranceEvoKasko: z.number(),
insuranceFinGAP: z.number(),
insuranceKasko: z.number(),
insuranceKaskoNmper: z.number(),
insuranceOsago: z.number(),
irrExpected: z.number(),
lastPayment: z.number(),
lastPaymentFix: z.boolean(),
lastPaymentSum: z.number(),
leasing0K: z.number(),
loanRate: z.number(),
loanRatePeriod: z.number(),
marketRate: z.number(),
modelId: z.string().nullable(),
motorVolume: z.number(),
nmper: z.number(),
nmperDeprecation: z.number(),
nmperFinGAP: z.number(),
nmperInsurance: z.number(),
npvniDelta: z.number(),
npvniExpected: z.number(),
nsBonus: z.number(),
nsibBase: z.number(),
nsibBonus: z.number(),
nsibBrutto: z.number(),
nsibBruttoPr: z.number(),
nsibNetto: z.number(),
nsibNettoPr: z.number(),
paymentDateNew: z.date().nullable(),
plEngineType: z.number().nullable(),
plPrice: z.number(),
plPriceVAT: z.number(),
plPriceWithVAT: z.number(),
plTypeId: z.string().nullable(),
plYear: z.number(),
profitExpected: z.number(),
ratBonus: z.number(),
rats: z.number(),
regionalDirectorBonus: z.number(),
regionalDirectorBonusFinGAP: z.number(),
regionalDirectorBonusFix: z.number(),
regionalDirectorBonusNsib: z.number(),
regionalDirectorExtraBonus: z.number(),
registration: z.number(),
repayment: z.number(),
retroBonus: z.number(),
salaryRate: z.number(),
scheduleOfPayments: z.number(),
subsidyPaymentNumber: z.number(),
subsidySum: z.number(),
supplierFinancing: z.boolean(),
tLMCost: z.number(),
tlmCostPaymentSum: z.number(),
totalExpected: z.number(),
trackerCost: z.number(),
transIncludeGr: z.boolean(),
transTax: z.number(),
transportTaxGr: z.number(),
transportTaxGrYear: z.number(),
});
export type PreparedValues = z.infer<typeof PreparedValuesSchema>;
const PaymentRowSchema = z.object({
gpsBasePayment: z.number(),
gpsCostPayment: z.number(),
numberPayment: z.number(),
percentPayment: z.number(),
sumPayment: z.number(),
tlmBasePayment: z.number(),
tlmCostPayment: z.number(),
});
const PreparedPaymentSchema = z.object({
rows: PaymentRowSchema.array(),
});
export type PreparedPayments = z.infer<typeof PreparedPaymentSchema>;
const AdditionalDataSchema = z.object({
maxAllAgencyPerc: z.number().nullable(),
maxCashflowMSFONominal: z.number().nullable(),
minCashflowMSFONominal: z.number().nullable(),
});
export type AdditionalData = z.infer<typeof AdditionalDataSchema>;
export const CalculateRequestSchema = z.object({
additionalData: AdditionalDataSchema,
preparedPayments: PreparedPaymentSchema,
preparedValues: PreparedValuesSchema,
});
export type RequestCalculate = z.infer<typeof CalculateRequestSchema>;
const PostValuesSchema = z.object({
baseCost: z.number(),
bonusBase: z.number(),
bonusResult: z.number(),
contractEconomy: z.number(),
contractEconomyWithVAT: z.number(),
directorBonus: z.number(),
directorExtraBonus: z.number(),
npvni: z.number(),
priceUP: z.number(),
priceUP_PR: z.number(),
priceUP_Year: z.number(),
priceUP_Year_PR: z.number(),
regionalDirectorBonus: z.number(),
regionalDirectorExtraBonus: z.number(),
});
const ColumnsSchema = z.object({
acceptInsuranceColumn: z.object({
sum: z.number(),
values: z.number().array(),
}),
acceptKaskoColumn: z.object({
sum: z.number(),
values: z.number().array(),
}),
acceptOsagoColumn: z.object({
sum: z.number(),
values: z.number().array(),
}),
acceptSumColumn: z.object({
sum: z.number(),
values: z.number().array(),
}),
agentComissionExpensesColumn: z.object({
sum: z.number(),
values: z.number().array(),
}),
cashflowColumn: z.object({
dates: z.array(z.string()),
irr: z.number(),
values: z.number().array(),
}),
cashflowLeasingColumn: z.object({
dates: z.array(z.string()),
irr: z.number(),
values: z.number().array(),
}),
cashflowMsfoColumn: z.object({
dates: z.array(z.string()),
irr: z.number(),
nominal: z.number(),
values: z.number().array(),
}),
cashflowMsfoFinal2Column: z.object({
dates: z.array(z.string()),
irr: z.number(),
nominal: z.number(),
values: z.number().array(),
}),
cashflowMsfoFinalColumn: z.object({
dates: z.array(z.string()),
irr: z.number(),
nominal: z.number(),
values: z.number().array(),
}),
cashflowNpvColumn: z.object({
values: z.number().array(),
}),
cashflowNpvFinal2Column: z.object({
values: z.number().array(),
}),
cashflowNpvFinalColumn: z.object({
values: z.number().array(),
}),
cashflowNsibColumn: z.object({
dates: z.array(z.string()),
irr: z.number(),
nominal: z.number(),
values: z.number().array(),
}),
comissionBonusExpensesColumn: z.object({
sum: z.number(),
values: z.number().array(),
}),
creditColumn: z.object({
sum: z.number(),
values: z.number().array(),
}),
creditVATColumn: z.object({
sum: z.number(),
values: z.number().array(),
}),
dateColumn: z.object({
values: z.array(z.string()),
}),
dateTempColumn: z.object({
values: z.array(z.string()),
}),
deprecationColumn: z.object({
sum: z.number(),
values: z.number().array(),
}),
deprecationLdColumn: z.object({
sum: z.number(),
values: z.number().array(),
}),
deprecationLpColumn: z.object({
sum: z.number(),
values: z.number().array(),
}),
directorBonusSumColumn: z.object({
sum: z.number(),
values: z.number().array(),
}),
evoKaskoNmperGrColumn: z.object({
sum: z.number(),
values: z.number().array(),
}),
expensesColumn: z.object({
sum: z.number(),
values: z.number().array(),
}),
extraBonusSumColumn: z.object({
sum: z.number(),
values: z.number().array(),
}),
finGAPNmperGrColumn: z.object({
sum: z.number(),
values: z.number().array(),
}),
gpsExpensesColumn: z.object({
sum: z.number(),
values: z.number().array(),
}),
gpsGrColumn: z.object({
sum: z.number(),
values: z.number().array(),
}),
insuranceBonusExpensesColumn: z.object({
sum: z.number(),
values: z.number().array(),
}),
interestColumn: z.object({
values: z.number().array(),
}),
irrGrColumn: z.object({
values: z.number().array(),
}),
kaskoBonusGrSumColumn: z.object({
sum: z.number(),
values: z.number().array(),
}),
kaskoNmperGrColumn: z.object({
sum: z.number(),
values: z.number().array(),
}),
negativeCashflowColumn: z.object({
sum: z.number(),
values: z.number().array(),
}),
niColumn: z.object({
values: z.number().array(),
}),
npvBonusExpensesColumn: z.object({
sum: z.number(),
values: z.number().array(),
}),
npvColumn: z.object({
dates: z.array(z.string()),
irr: z.number(),
nominal: z.number(),
values: z.number().array(),
}),
npvFinal2Column: z.object({
dates: z.array(z.string()),
irr: z.number(),
nominal: z.number(),
values: z.number().array(),
}),
npvFinalColumn: z.object({
dates: z.array(z.string()),
irr: z.number(),
nominal: z.number(),
values: z.number().array(),
}),
npvWeightColumn: z.object({
values: z.number().array(),
}),
nsibBruttoGrColumn: z.object({
sum: z.number(),
values: z.number().array(),
}),
nsibExpensesColumn: z.object({
sum: z.number(),
values: z.number().array(),
}),
percentPaymentColumn: z.object({
values: z.number().array(),
}),
ratExpensesColumn: z.object({
sum: z.number(),
values: z.number().array(),
}),
registrExpensesColumn: z.object({
sum: z.number(),
values: z.number().array(),
}),
revenueColumn: z.object({
values: z.number().array(),
}),
subsidyExpensesColumn: z.object({
sum: z.number(),
values: z.number().array(),
}),
sumColumn: z.object({
dates: z.array(z.string()),
irr: z.number(),
values: z.number().array(),
}),
sumCreditColumn: z.object({
values: z.number().array(),
}),
sumCurrentColumn: z.object({
values: z.number().array(),
}),
sumCurrentInterestColumn: z.object({
values: z.number().array(),
}),
sumCurrentNegativeColumn: z.object({
values: z.number().array(),
}),
sumCurrentTlmColumn: z.object({
values: z.number().array(),
}),
sumRepaymentColumn: z.object({
values: z.number().array(),
}),
sumVATCreditColumn: z.object({
values: z.number().array(),
}),
sumWithVatColumn: z.object({
sum: z.number(),
values: z.number().array(),
}),
taxColumn: z.object({
sum: z.number(),
values: z.number().array(),
}),
tlmExpensesColumn: z.object({
sum: z.number(),
values: z.number().array(),
}),
tlmGrColumn: z.object({
sum: z.number(),
values: z.number().array(),
}),
transExprensesColumn: z.object({
sum: z.number(),
values: z.number().array(),
}),
vatColumn: z.object({
sum: z.number(),
values: z.number().array(),
}),
});
export const ResponseCalculateSchema = z.object({
columns: ColumnsSchema,
errors: z.string().array(),
postValues: PostValuesSchema,
preparedValues: PreparedValuesSchema.extend({
insuranceFinGAPNmper: z.number(),
niAtInception: z.number(),
}),
});
export type ResponseCalculate = z.infer<typeof ResponseCalculateSchema>;

View File

@ -0,0 +1,2 @@
export * from './calculate';
export * from './fingap';

View File

@ -5,6 +5,7 @@ const envSchema = z.object({
COOKIE_TOKEN_NAME: z.string().optional().default('token'),
PORT: z.string().optional(),
URL_1C_TRANSTAX_DIRECT: z.string(),
URL_CORE_CALCULATE_DIRECT: z.string(),
URL_CORE_FINGAP_DIRECT: z.string(),
URL_CRM_GRAPHQL_DIRECT: z.string(),
URL_GET_USER_DIRECT: z.string(),

View File

@ -0,0 +1,33 @@
import { z } from 'zod';
export const ResultValuesSchema = z.object({
resultAB_FL: z.number(),
resultAB_UL: z.number(),
resultBonusDopProd: z.number(),
resultBonusMPL: z.number(),
resultBonusSafeFinance: z.number(),
resultDopMPLLeasing: z.number(),
resultDopProdSum: z.number(),
resultFirstPayment: z.number(),
resultFirstPaymentRiskPolicy: z.number(),
resultIRRGraphPerc: z.number(),
resultIRRNominalPerc: z.number(),
resultInsKasko: z.number(),
resultInsOsago: z.number(),
resultLastPayment: z.number(),
resultParticipationAmount: z.number(),
resultPlPrice: z.number(),
resultPriceUpPr: z.number(),
resultTerm: z.number(),
resultTotalGraphwithNDS: z.number(),
});
export const ResultPaymentSchema = z.object({
key: z.string(),
ndsCompensation: z.number(),
num: z.number(),
paymentSum: z.number(),
redemptionAmount: z.number(),
});
export const ResultPaymentsSchema = ResultPaymentSchema.array();

View File

@ -11,6 +11,7 @@ const serverRuntimeConfigSchema = envSchema.pick({
COOKIE_TOKEN_NAME: true,
PORT: true,
URL_1C_TRANSTAX_DIRECT: true,
URL_CORE_CALCULATE_DIRECT: true,
URL_CORE_FINGAP_DIRECT: true,
URL_CRM_GRAPHQL_DIRECT: true,
URL_GET_USER_DIRECT: true,

View File

@ -14,6 +14,7 @@ function getUrls() {
URL_GET_USER_DIRECT,
URL_CORE_FINGAP_DIRECT,
URL_1C_TRANSTAX_DIRECT,
URL_CORE_CALCULATE_DIRECT,
PORT,
} = serverRuntimeConfigSchema.parse(serverRuntimeConfig);
@ -21,6 +22,7 @@ function getUrls() {
BASE_PATH,
PORT,
URL_1C_TRANSTAX: URL_1C_TRANSTAX_DIRECT,
URL_CORE_CALCULATE: URL_CORE_CALCULATE_DIRECT,
URL_CORE_FINGAP: URL_CORE_FINGAP_DIRECT,
URL_CRM_GRAPHQL: URL_CRM_GRAPHQL_DIRECT,
URL_GET_USER: URL_GET_USER_DIRECT,
@ -34,6 +36,7 @@ function getUrls() {
return {
BASE_PATH,
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_GRAPHQL: withBasePath(urls.URL_CRM_GRAPHQL_PROXY),
URL_GET_USER: withBasePath(urls.URL_GET_USER_PROXY),

View File

@ -1,5 +1,6 @@
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_GRAPHQL_PROXY: '/api/graphql/crm',
URL_GET_USER_PROXY: '/api/auth/user',

View File

@ -11,3 +11,4 @@ export const MAX_MASS = 3500;
export const MAX_VEHICLE_SEATS = 20;
export const ESN = 1.3;
export const NSIB_MAX = 5_000_000;
export const NDFL = 0.13;

View File

@ -0,0 +1,48 @@
import { toJS } from 'mobx';
import notification from 'ui/elements/notification';
const key = 'ACTION_CALCULATE';
const message = 'Ошибка во время расчета графика';
/**
* @param {import('../types').ProcessContext} context
* @param {*} onCompleted
*/
export async function action({ store, trpcClient }) {
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 payments = toJS($tables.payments.values);
const res = await trpcClient.calculate.calculate.query({
insurance: { values: insurance },
payments: { values: payments },
values,
});
if (res.success === false) {
notification.error({
description: res.error,
key,
message,
});
} else {
$results.setPayments(res.data.resultPayments);
$results.setValues(res.data.resultValues);
$calculation.$values.setValues(res.data.values);
}
$calculation.$status.setStatus('btnCalculate', 'Default');
$calculation.$status.setStatus('btnCreateKP', 'Default');
}

View File

@ -1,34 +0,0 @@
import type { ProcessContext } from '../types';
import { toJS } from 'mobx';
import notification from 'ui/elements/notification';
const key = 'ACTION_CALCULATE';
const message = 'Ошибка во время расчета графика';
export async function action({ store, trpcClient }: ProcessContext) {
const { $calculation, $tables } = store;
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 payments = toJS($tables.payments.values);
const { error, success } = await trpcClient.calculate.calculate.query({
insurance: { values: insurance },
payments: { values: payments },
values,
});
if (!success) {
notification.error({
description: error,
key,
message,
});
}
}

View File

@ -1,6 +1,6 @@
import type { ResultsValues } from './types';
import type { ResultValues } from './types';
export const defaultResultsValues: ResultsValues = {
export const defaultResultsValues: ResultValues = {
resultAB_FL: 0,
resultAB_UL: 0,
resultBonusDopProd: 0,
@ -15,6 +15,7 @@ export const defaultResultsValues: ResultsValues = {
resultInsKasko: 0,
resultInsOsago: 0,
resultLastPayment: 0,
resultParticipationAmount: 0,
resultPlPrice: 0,
resultPriceUpPr: 0,
resultTerm: 0,

View File

@ -1,28 +1,27 @@
import { defaultResultsValues } from './default-values';
import type { ResultsValues } from './types';
import type { Payment } from '@/Components/Output/PaymentsTable/types';
import type { ResultPayment, ResultValues } from './types';
import type RootStore from '@/stores/root';
import type { IObservableArray } from 'mobx';
import { makeAutoObservable, observable } from 'mobx';
export default class Results {
private root: RootStore;
public payments: IObservableArray<Payment>;
public values: ResultsValues;
public payments: IObservableArray<ResultPayment>;
public values: ResultValues;
constructor(rootStore: RootStore) {
this.payments = observable<Payment>([]);
this.payments = observable<ResultPayment>([]);
this.values = defaultResultsValues;
makeAutoObservable(this);
this.root = rootStore;
}
public setPayments = (payments: Payment[]) => {
public setPayments = (payments: ResultPayment[]) => {
this.payments.replace(payments);
};
public setValues = (values: ResultsValues) => {
public setValues = (values: ResultValues) => {
this.values = values;
};

View File

@ -1,22 +1,12 @@
export type ResultsValues = {
resultAB_FL: number;
resultAB_UL: number;
resultBonusDopProd: number;
resultBonusMPL: number;
resultBonusSafeFinance: number;
resultDopMPLLeasing: number;
resultDopProdSum: number;
resultFirstPayment: number;
resultFirstPaymentRiskPolicy: number;
resultIRRGraphPerc: number;
resultIRRNominalPerc: number;
resultInsKasko: number;
resultInsOsago: number;
resultLastPayment: number;
resultPlPrice: number;
resultPriceUpPr: number;
resultTerm: number;
resultTotalGraphwithNDS: number;
};
import type {
ResultPaymentSchema,
ResultPaymentsSchema,
ResultValuesSchema,
} from '@/config/schema/results';
import type { z } from 'zod';
export type Values = keyof ResultsValues;
export type ResultValues = z.infer<typeof ResultValuesSchema>;
export type ResultPayment = z.infer<typeof ResultPaymentSchema>;
export type ResultPayments = z.infer<typeof ResultPaymentsSchema>;

View File

@ -0,0 +1,92 @@
import type { CalculateInput, OutputData } from './types';
import type { RequestCalculate, ResponseCalculate } from '@/api/core/types';
import { ESN, NDFL, VAT } from '@/constants/values';
import { last } from 'radash';
type Input = {
calculateInput: CalculateInput;
requestCalculate: RequestCalculate;
responseCalculate: ResponseCalculate;
};
export function convertCalculateResults({ responseCalculate, calculateInput }: Input): OutputData {
const { values: inputValues } = calculateInput;
const { postValues, columns, preparedValues } = responseCalculate;
const resultPayments: OutputData['resultPayments'] = Array.from(
{
length: preparedValues.nmper,
},
(_, i) => ({
key: String(i + 1),
ndsCompensation: columns.vatColumn.values[i + 1],
num: i + 1,
paymentSum: columns.sumWithVatColumn.values[i + 1],
redemptionAmount: columns.sumRepaymentColumn.values[i + 1],
})
);
const resultValues: OutputData['resultValues'] = {
resultAB_FL: ((preparedValues.agentsSum + preparedValues.doubleAgentsSum) / ESN) * (1 - NDFL),
resultAB_UL:
(preparedValues.deliverySum +
preparedValues.brokerSum +
preparedValues.brokerOfDeliverySum +
preparedValues.financialDeptOfDeliverySum) *
(1 + VAT),
resultBonusDopProd: Math.abs(
(columns?.npvBonusExpensesColumn?.values[1] / (1 + preparedValues?.salaryRate)) * (1 - NDFL)
),
resultBonusMPL: Math.abs(
(columns.npvBonusExpensesColumn.values[2] / (1 + preparedValues.salaryRate)) * (1 - NDFL)
),
resultBonusSafeFinance: preparedValues?.bonusFinGAP * (1 - NDFL),
resultDopMPLLeasing: Math.abs(
(columns.extraBonusSumColumn.values[2] / (1 + preparedValues.salaryRate)) * (1 - NDFL)
),
resultDopProdSum: 0,
// resultDopProdSum: preparedValues.rats +
// preparedValues.registration +
// preparedValues.trackerCost +
// preparedValues.tLMCost +
// preparedValues.nsibBrutto +
// preparedValues.insuranceFinGAPNmper,
resultFirstPayment: preparedValues.firstPaymentSum * (1 + VAT) - inputValues.subsidySum,
resultFirstPaymentRiskPolicy: preparedValues?.firstPayment * 100,
resultIRRGraphPerc: columns.sumColumn.irr * 100,
resultIRRNominalPerc: columns.cashflowMsfoColumn.nominal * 100,
resultInsKasko: preparedValues.insuranceKasko,
resultInsOsago: preparedValues.insuranceOsago,
resultLastPayment: preparedValues.lastPaymentSum * (1 + VAT),
resultParticipationAmount:
preparedValues.niAtInception +
(preparedValues.ratBonus + preparedValues.nsBonus + preparedValues.nsibBonus) *
preparedValues.marketRate *
preparedValues.districtRate +
Math.abs(columns.npvBonusExpensesColumn.values[0]) +
Math.abs(
columns.extraBonusSumColumn.values[0] +
preparedValues.importerSum +
preparedValues.agentsSum +
preparedValues.deliverySum +
preparedValues.brokerSum +
preparedValues.brokerOfDeliverySum +
preparedValues.financialDeptOfDeliverySum
),
resultPlPrice:
preparedValues.plPriceWithVAT -
inputValues.supplierDiscountRub -
inputValues.importProgramSum,
resultPriceUpPr: postValues.priceUP_Year_PR * 100,
resultTerm: preparedValues.nmper,
resultTotalGraphwithNDS: columns.sumWithVatColumn.values[0] - inputValues.subsidySum,
};
const values: OutputData['values'] = {
IRR_Perc: columns?.cashflowMsfoColumn?.nominal * 100,
lastPaymentRub: last(columns?.sumWithVatColumn?.values) || 0,
totalPayments: columns?.sumWithVatColumn?.values[0] - inputValues.subsidySum,
};
return { resultPayments, resultValues, values };
}

View File

@ -1,9 +1,12 @@
import { user } from '../../middleware';
import { t } from '../../server';
import { convertCalculateResults } from './convert';
import { getRequestData } from './request';
import { CalculateInputSchema, CalculateOutputSchema } from './types';
import { validate } from './validation';
import { calculate } from '@/api/core/query';
import initializeApollo from '@/apollo/client';
import type { QueryFunctionContext } from '@tanstack/react-query';
import { QueryClient } from '@tanstack/react-query';
const calculateRouter = t.router({
@ -15,7 +18,7 @@ const calculateRouter = t.router({
const apolloClient = initializeApollo();
const queryClient = new QueryClient();
const { success, error } = await validate({
const validationResult = await validate({
context: {
apolloClient,
queryClient,
@ -23,10 +26,10 @@ const calculateRouter = t.router({
input,
});
if (!success) {
if (validationResult.success === false) {
return {
error,
success,
error: validationResult.error,
success: false,
};
}
@ -39,9 +42,28 @@ const calculateRouter = t.router({
user: ctx.user,
});
const request = (context: QueryFunctionContext) => calculate(payload, context);
const calculateResult = await queryClient.fetchQuery(['calculate'], request, {
staleTime: 15_000,
});
if (calculateResult.errors?.length > 0) {
return {
error: calculateResult.errors[0],
success: false,
};
}
const result = convertCalculateResults({
calculateInput: input,
requestCalculate: payload,
responseCalculate: calculateResult,
});
return {
error: undefined,
success,
data: result,
success: true,
};
}),
});

View File

@ -1,5 +1,6 @@
/* eslint-disable sonarjs/cognitive-complexity */
import type * as Types from './types';
import type { CalculateInput, Context } from './types';
import type * as CoreTypes from '@/api/core/types';
import type { User } from '@/api/user/types';
import { ESN, NSIB_MAX, VAT } from '@/constants/values';
import * as CRMTypes from '@/graphql/crm.types';
@ -12,30 +13,30 @@ import { max, sum } from 'radash';
dayjs.extend(utc);
type Input = {
context: Types.Context;
input: Types.CalculateInput;
context: Context;
input: CalculateInput;
user: User;
};
type PreparedValuesGetters = {
[Key in keyof Types.PreparedValues]: () => Promise<Types.PreparedValues[Key]>;
[Key in keyof CoreTypes.PreparedValues]: () => Promise<CoreTypes.PreparedValues[Key]>;
};
type AdditionalDataGetters = {
[Key in keyof Types.AdditionalData]: () => Promise<Types.AdditionalData[Key]>;
[Key in keyof CoreTypes.AdditionalData]: () => Promise<CoreTypes.AdditionalData[Key]>;
};
export async function getRequestData({
context,
input,
user,
}: Input): Promise<Types.CalculateRequest> {
}: Input): Promise<CoreTypes.RequestCalculate> {
const { apolloClient } = context;
const { values, insurance, payments } = input;
const { RUB } = createCurrencyUtility({ apolloClient });
const preparedPayments: Types.CalculateRequest['preparedPayments'] = {
const preparedPayments: CoreTypes.PreparedPayments = {
rows: payments.values.map((payment, index) => ({
gpsBasePayment: 0,
gpsCostPayment: 0,
@ -160,7 +161,7 @@ export async function getRequestData({
variables: { addproductTypeId: values.technicalCard },
});
return evo_addproduct_type?.evo_cost_service_provider_withoutnds ?? 0;
return evo_addproduct_type?.evo_cost_service_provider_withoutnds || 0;
}
return 0;
@ -175,7 +176,7 @@ export async function getRequestData({
variables: { addproductTypeId: values.registration },
});
return evo_addproduct_type?.evo_cost_service_provider_withoutnds ?? 0;
return evo_addproduct_type?.evo_cost_service_provider_withoutnds || 0;
}
return 0;
@ -1000,7 +1001,7 @@ export async function getRequestData({
variables: { addproductTypeId: values.technicalCard },
});
return evo_addproduct_type?.evo_graph_price_withoutnds ?? 0;
return evo_addproduct_type?.evo_graph_price_withoutnds || 0;
}
return 0;
@ -1060,7 +1061,7 @@ export async function getRequestData({
variables: { addproductTypeId: values.registration },
});
return evo_addproduct_type?.evo_graph_price_withoutnds ?? 0;
return evo_addproduct_type?.evo_graph_price_withoutnds || 0;
}
return 0;
@ -1079,7 +1080,7 @@ export async function getRequestData({
variables: { addproductTypeId: values.technicalCard },
});
return evo_addproduct_type?.evo_retro_bonus_withoutnds ?? 0;
return evo_addproduct_type?.evo_retro_bonus_withoutnds || 0;
}
return 0;
@ -1129,7 +1130,7 @@ export async function getRequestData({
variables: { addproductTypeId: values.telematic },
});
return evo_addproduct_type?.evo_graph_price_withoutnds ?? 0;
return evo_addproduct_type?.evo_graph_price_withoutnds || 0;
}
return 0;
@ -1152,7 +1153,7 @@ export async function getRequestData({
variables: { addproductTypeId: values.tracker },
});
return evo_addproduct_type?.evo_graph_price_withoutnds ?? 0;
return evo_addproduct_type?.evo_graph_price_withoutnds || 0;
}
return 0;
@ -1177,7 +1178,7 @@ export async function getRequestData({
const preparedValuesResult = await Promise.all(
(Object.keys(preparedValuesGetters) as Array<keyof PreparedValuesGetters>).map(
async <T extends keyof Types.PreparedValues>(key: T) => {
async <T extends keyof CoreTypes.PreparedValues>(key: T) => {
const value = await preparedValuesGetters[key]();
return { [key]: value };
@ -1185,7 +1186,7 @@ export async function getRequestData({
)
);
const preparedValues = Object.assign({}, ...preparedValuesResult) as Types.PreparedValues;
const preparedValues = Object.assign({}, ...preparedValuesResult) as CoreTypes.PreparedValues;
const { getIrr } = helper({ apolloClient });
const irr = await getIrr(values);
@ -1215,7 +1216,7 @@ export async function getRequestData({
const additionalDataResult = await Promise.all(
(Object.keys(additionalDataGetters) as Array<keyof AdditionalDataGetters>).map(
async <T extends keyof Types.AdditionalData>(key: T) => {
async <T extends keyof CoreTypes.AdditionalData>(key: T) => {
const value = await additionalDataGetters[key]();
return { [key]: value };
@ -1223,7 +1224,7 @@ export async function getRequestData({
)
);
const additionalData = Object.assign({}, ...additionalDataResult) as Types.AdditionalData;
const additionalData = Object.assign({}, ...additionalDataResult) as CoreTypes.AdditionalData;
return {
additionalData,

View File

@ -1,5 +1,6 @@
import { InsuranceSchema } from '@/config/schema/insurance';
import PaymentsSchema from '@/config/schema/payments';
import { ResultPaymentsSchema, ResultValuesSchema } from '@/config/schema/results';
import ValuesSchema from '@/config/schema/values';
import type { ProcessContext } from '@/process/types';
import { z } from 'zod';
@ -16,154 +17,28 @@ export const CalculateInputSchema = z
export type CalculateInput = z.infer<typeof CalculateInputSchema>;
export const CalculateOutputSchema = z.object({
error: z.string().optional(),
success: z.boolean(),
export const OutputDataSchema = z.object({
resultPayments: ResultPaymentsSchema,
resultValues: ResultValuesSchema,
values: ValuesSchema.pick({
IRR_Perc: true,
lastPaymentRub: true,
totalPayments: true,
}),
});
export type OutputData = z.infer<typeof OutputDataSchema>;
export const CalculateOutputSchema = z
.object({
data: OutputDataSchema,
success: z.boolean(),
})
.or(
z.object({
error: z.string(),
success: z.boolean(),
})
);
export type CalculateOutput = z.infer<typeof CalculateOutputSchema>;
export const PreparedValuesSchema = z.object({
acceptSum: z.number(),
agentsSum: z.number(),
balanceHolder: z.number(),
baseRatCost: z.number(),
baseRegistration: z.number(),
bonus: z.number(),
bonusCoefficient: z.number(),
bonusFinGAP: z.number(),
bonusFix: z.number(),
bonusNsPr: z.number(),
bonusNsibPr: z.number(),
bonusRatPr: z.number(),
brandId: z.string().nullable(),
brokerOfDeliverySum: z.number(),
brokerSum: z.number(),
calcDate: z.date(),
calcType: z.number(),
carCapacity: z.number(),
carCarrying: z.number(),
carSeats: z.number(),
cityc: z.string(),
comissionRub: z.number(),
configurationId: z.string().nullable(),
deliverySum: z.number(),
deliveryTime: z.number(),
deprecationTime: z.number(),
directorBonus: z.number(),
directorBonusFinGAP: z.number(),
directorBonusFix: z.number(),
directorBonusNsib: z.number(),
directorExtraBonus: z.number(),
discount: z.number(),
districtRate: z.number(),
doubleAgentsSum: z.number(),
extraBonus: z.number(),
financialDeptOfDeliverySum: z.number(),
firstPayment: z.number(),
firstPaymentAbs: z.number(),
firstPaymentNdsAbs: z.number(),
firstPaymentSum: z.number(),
firstPaymentWithNdsAbs: z.number(),
fuelCardSum: z.number(),
gpsCostPaymentSum: z.number(),
iRR_MSFO_Plan: z.number(),
importProgramSum: z.number(),
importerSum: z.number(),
insuranceBonus: z.number(),
insuranceBonusLoss: z.number(),
insuranceContract: z.number(),
insuranceEvoKasko: z.number(),
insuranceFinGAP: z.number(),
insuranceKasko: z.number(),
insuranceKaskoNmper: z.number(),
insuranceOsago: z.number(),
irrExpected: z.number(),
lastPayment: z.number(),
lastPaymentFix: z.boolean(),
lastPaymentSum: z.number(),
leasing0K: z.number(),
loanRate: z.number(),
loanRatePeriod: z.number(),
marketRate: z.number(),
modelId: z.string().nullable(),
motorVolume: z.number(),
nmper: z.number(),
nmperDeprecation: z.number(),
nmperFinGAP: z.number(),
nmperInsurance: z.number(),
npvniDelta: z.number(),
npvniExpected: z.number(),
nsBonus: z.number(),
nsibBase: z.number(),
nsibBonus: z.number(),
nsibBrutto: z.number(),
nsibBruttoPr: z.number(),
nsibNetto: z.number(),
nsibNettoPr: z.number(),
paymentDateNew: z.date().nullable(),
plEngineType: z.number().nullable(),
plPrice: z.number(),
plPriceVAT: z.number(),
plPriceWithVAT: z.number(),
plTypeId: z.string().nullable(),
plYear: z.number(),
profitExpected: z.number(),
ratBonus: z.number(),
rats: z.number(),
regionalDirectorBonus: z.number(),
regionalDirectorBonusFinGAP: z.number(),
regionalDirectorBonusFix: z.number(),
regionalDirectorBonusNsib: z.number(),
regionalDirectorExtraBonus: z.number(),
registration: z.number(),
repayment: z.number(),
retroBonus: z.number(),
salaryRate: z.number(),
scheduleOfPayments: z.number(),
subsidyPaymentNumber: z.number(),
subsidySum: z.number(),
supplierFinancing: z.boolean(),
tLMCost: z.number(),
tlmCostPaymentSum: z.number(),
totalExpected: z.number(),
trackerCost: z.number(),
transIncludeGr: z.boolean(),
transTax: z.number(),
transportTaxGr: z.number(),
transportTaxGrYear: z.number(),
});
export type PreparedValues = z.infer<typeof PreparedValuesSchema>;
const PaymentRowSchema = z.object({
gpsBasePayment: z.number(),
gpsCostPayment: z.number(),
numberPayment: z.number(),
percentPayment: z.number(),
sumPayment: z.number(),
tlmBasePayment: z.number(),
tlmCostPayment: z.number(),
});
const PreparedPaymentSchema = z.object({
rows: PaymentRowSchema.array(),
});
export type PreparedPayments = z.infer<typeof PreparedPaymentSchema>;
const AdditionalDataSchema = z.object({
maxAllAgencyPerc: z.number().nullable(),
maxCashflowMSFONominal: z.number().nullable(),
minCashflowMSFONominal: z.number().nullable(),
});
export type AdditionalData = z.infer<typeof AdditionalDataSchema>;
export const CalculateRequestSchema = z.object({
additionalData: AdditionalDataSchema,
preparedPayments: PreparedPaymentSchema,
preparedValues: PreparedValuesSchema,
});
export type CalculateRequest = z.infer<typeof CalculateRequestSchema>;

View File

@ -1,5 +1,4 @@
import type { CalculateInput, Context } from './types';
import { CalculateOutputSchema } from './types';
import elementsTitles from '@/Components/Calculation/config/elements-titles';
import type { Elements } from '@/Components/Calculation/config/map/values';
import * as bonuses from '@/process/bonuses';
@ -10,7 +9,7 @@ import * as leasingObject from '@/process/leasing-object';
import * as paymentsProcess from '@/process/payments';
import * as price from '@/process/price';
import * as supplierAgent from '@/process/supplier-agent';
import type { z, ZodIssue } from 'zod';
import type { ZodIssue } from 'zod';
const processes = [
configurator,
@ -36,20 +35,7 @@ function getMessage(errors: ZodIssue[]) {
return `${title}: ${message}`;
}
const ValidationOutputSchema = CalculateOutputSchema.pick({
error: true,
success: true,
});
type ValidationOutput = z.infer<typeof ValidationOutputSchema>;
export async function validate({
input,
context,
}: {
context: Context;
input: CalculateInput;
}): Promise<ValidationOutput> {
export async function validate({ input, context }: { context: Context; input: CalculateInput }) {
for (const { createValidationSchema } of processes) {
const validationSchema = createValidationSchema(context);
const validationResult = await validationSchema.safeParseAsync({
@ -67,5 +53,6 @@ export async function validate({
return {
success: true,
error: '',
};
}

View File

@ -19,6 +19,17 @@ export const formatterExtra = (value?: number) =>
minimumFractionDigits: 2,
}).format(value || 0);
export const moneyFormatter = Intl.NumberFormat('ru', {
currency: 'RUB',
style: 'currency',
}).format;
export const percentFormatter = Intl.NumberFormat('ru', {
maximumFractionDigits: 2,
minimumFractionDigits: 2,
style: 'percent',
}).format;
export function round(value: number, precision: number = 0) {
return Number.parseFloat(value.toFixed(precision));
}