From dbe6bdfe16b1a3a412e27e2492283129a00b61e6 Mon Sep 17 00:00:00 2001 From: vchikalkin Date: Wed, 22 Mar 2023 21:09:43 +0300 Subject: [PATCH] calculate pt.3: it's calculate!!! --- .env | 1 + .../Components/Output/PaymentsTable/config.ts | 4 +- .../Components/Output/PaymentsTable/types.ts | 7 - apps/web/Components/Output/Results/config.ts | 71 +-- apps/web/api/core/query.ts | 12 +- apps/web/api/core/types/calculate.ts | 425 ++++++++++++++++++ .../api/core/{types.ts => types/fingap.ts} | 0 apps/web/api/core/types/index.ts | 2 + apps/web/config/schema/env.js | 1 + apps/web/config/schema/results.ts | 33 ++ apps/web/config/schema/runtime-config.js | 1 + apps/web/config/urls.ts | 3 + apps/web/constants/urls.js | 1 + apps/web/constants/values.js | 1 + apps/web/process/calculate/action.js | 48 ++ apps/web/process/calculate/action.ts | 34 -- apps/web/stores/results/default-values.ts | 5 +- apps/web/stores/results/index.ts | 13 +- apps/web/stores/results/types.ts | 32 +- apps/web/trpc/routers/calculate/convert.ts | 92 ++++ apps/web/trpc/routers/calculate/index.ts | 34 +- apps/web/trpc/routers/calculate/request.ts | 37 +- apps/web/trpc/routers/calculate/types.ts | 171 +------ apps/web/trpc/routers/calculate/validation.ts | 19 +- packages/tools/number.ts | 11 + 25 files changed, 748 insertions(+), 310 deletions(-) delete mode 100644 apps/web/Components/Output/PaymentsTable/types.ts create mode 100644 apps/web/api/core/types/calculate.ts rename apps/web/api/core/{types.ts => types/fingap.ts} (100%) create mode 100644 apps/web/api/core/types/index.ts create mode 100644 apps/web/config/schema/results.ts create mode 100644 apps/web/process/calculate/action.js delete mode 100644 apps/web/process/calculate/action.ts create mode 100644 apps/web/trpc/routers/calculate/convert.ts diff --git a/.env b/.env index 4076e3a..10d2a37 100644 --- a/.env +++ b/.env @@ -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 ######## diff --git a/apps/web/Components/Output/PaymentsTable/config.ts b/apps/web/Components/Output/PaymentsTable/config.ts index f542472..0cba7bc 100644 --- a/apps/web/Components/Output/PaymentsTable/config.ts +++ b/apps/web/Components/Output/PaymentsTable/config.ts @@ -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 = [ +export const columns: ColumnsType = [ { key: 'num', dataIndex: 'num', diff --git a/apps/web/Components/Output/PaymentsTable/types.ts b/apps/web/Components/Output/PaymentsTable/types.ts deleted file mode 100644 index 020fb14..0000000 --- a/apps/web/Components/Output/PaymentsTable/types.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type Payment = { - key: string; - ndsCompensation: string; - num: number; - paymentSum: string; - redemptionAmount: string; -}; diff --git a/apps/web/Components/Output/Results/config.ts b/apps/web/Components/Output/Results/config.ts index 2210859..b073ab8 100644 --- a/apps/web/Components/Output/Results/config.ts +++ b/apps/web/Components/Output/Results/config.ts @@ -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 = { +export const titles: Record = { resultAB_FL: 'АВ ФЛ, без НДФЛ.', resultAB_UL: 'АВ ЮЛ, с НДС.', resultBonusDopProd: 'Бонус МПЛ за доп.продукты, без НДФЛ', @@ -18,55 +19,31 @@ export const titles: Record = { 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); diff --git a/apps/web/api/core/query.ts b/apps/web/api/core/query.ts index b0c3d46..215db4f 100644 --- a/apps/web/api/core/query.ts +++ b/apps/web/api/core/query.ts @@ -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(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(URL_CORE_CALCULATE, payload, { + signal, + }); + + return data; +} diff --git a/apps/web/api/core/types/calculate.ts b/apps/web/api/core/types/calculate.ts new file mode 100644 index 0000000..e880370 --- /dev/null +++ b/apps/web/api/core/types/calculate.ts @@ -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; + +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; + +const AdditionalDataSchema = z.object({ + maxAllAgencyPerc: z.number().nullable(), + maxCashflowMSFONominal: z.number().nullable(), + minCashflowMSFONominal: z.number().nullable(), +}); + +export type AdditionalData = z.infer; + +export const CalculateRequestSchema = z.object({ + additionalData: AdditionalDataSchema, + preparedPayments: PreparedPaymentSchema, + preparedValues: PreparedValuesSchema, +}); + +export type RequestCalculate = z.infer; + +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; diff --git a/apps/web/api/core/types.ts b/apps/web/api/core/types/fingap.ts similarity index 100% rename from apps/web/api/core/types.ts rename to apps/web/api/core/types/fingap.ts diff --git a/apps/web/api/core/types/index.ts b/apps/web/api/core/types/index.ts new file mode 100644 index 0000000..63795c7 --- /dev/null +++ b/apps/web/api/core/types/index.ts @@ -0,0 +1,2 @@ +export * from './calculate'; +export * from './fingap'; diff --git a/apps/web/config/schema/env.js b/apps/web/config/schema/env.js index f192725..889bd15 100644 --- a/apps/web/config/schema/env.js +++ b/apps/web/config/schema/env.js @@ -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(), diff --git a/apps/web/config/schema/results.ts b/apps/web/config/schema/results.ts new file mode 100644 index 0000000..326f70f --- /dev/null +++ b/apps/web/config/schema/results.ts @@ -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(); diff --git a/apps/web/config/schema/runtime-config.js b/apps/web/config/schema/runtime-config.js index 945d9ff..901f059 100644 --- a/apps/web/config/schema/runtime-config.js +++ b/apps/web/config/schema/runtime-config.js @@ -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, diff --git a/apps/web/config/urls.ts b/apps/web/config/urls.ts index 7805e49..532afe6 100644 --- a/apps/web/config/urls.ts +++ b/apps/web/config/urls.ts @@ -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), diff --git a/apps/web/constants/urls.js b/apps/web/constants/urls.js index 7fa1269..c6c42d9 100644 --- a/apps/web/constants/urls.js +++ b/apps/web/constants/urls.js @@ -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', diff --git a/apps/web/constants/values.js b/apps/web/constants/values.js index 046bcb9..34298ab 100644 --- a/apps/web/constants/values.js +++ b/apps/web/constants/values.js @@ -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; diff --git a/apps/web/process/calculate/action.js b/apps/web/process/calculate/action.js new file mode 100644 index 0000000..0d83006 --- /dev/null +++ b/apps/web/process/calculate/action.js @@ -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'); +} diff --git a/apps/web/process/calculate/action.ts b/apps/web/process/calculate/action.ts deleted file mode 100644 index 852f8b1..0000000 --- a/apps/web/process/calculate/action.ts +++ /dev/null @@ -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, - }); - } -} diff --git a/apps/web/stores/results/default-values.ts b/apps/web/stores/results/default-values.ts index 9316534..fb61969 100644 --- a/apps/web/stores/results/default-values.ts +++ b/apps/web/stores/results/default-values.ts @@ -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, diff --git a/apps/web/stores/results/index.ts b/apps/web/stores/results/index.ts index 96340ce..c553ae8 100644 --- a/apps/web/stores/results/index.ts +++ b/apps/web/stores/results/index.ts @@ -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; - public values: ResultsValues; + public payments: IObservableArray; + public values: ResultValues; constructor(rootStore: RootStore) { - this.payments = observable([]); + this.payments = observable([]); 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; }; diff --git a/apps/web/stores/results/types.ts b/apps/web/stores/results/types.ts index 46bd7ad..21051ca 100644 --- a/apps/web/stores/results/types.ts +++ b/apps/web/stores/results/types.ts @@ -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; + +export type ResultPayment = z.infer; + +export type ResultPayments = z.infer; diff --git a/apps/web/trpc/routers/calculate/convert.ts b/apps/web/trpc/routers/calculate/convert.ts new file mode 100644 index 0000000..2ef5953 --- /dev/null +++ b/apps/web/trpc/routers/calculate/convert.ts @@ -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 }; +} diff --git a/apps/web/trpc/routers/calculate/index.ts b/apps/web/trpc/routers/calculate/index.ts index ccafefd..550a464 100644 --- a/apps/web/trpc/routers/calculate/index.ts +++ b/apps/web/trpc/routers/calculate/index.ts @@ -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, }; }), }); diff --git a/apps/web/trpc/routers/calculate/request.ts b/apps/web/trpc/routers/calculate/request.ts index c61ebd7..5d7c1ca 100644 --- a/apps/web/trpc/routers/calculate/request.ts +++ b/apps/web/trpc/routers/calculate/request.ts @@ -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; + [Key in keyof CoreTypes.PreparedValues]: () => Promise; }; type AdditionalDataGetters = { - [Key in keyof Types.AdditionalData]: () => Promise; + [Key in keyof CoreTypes.AdditionalData]: () => Promise; }; export async function getRequestData({ context, input, user, -}: Input): Promise { +}: Input): Promise { 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).map( - async (key: T) => { + async (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).map( - async (key: T) => { + async (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, diff --git a/apps/web/trpc/routers/calculate/types.ts b/apps/web/trpc/routers/calculate/types.ts index 0eb7bdc..cd6405c 100644 --- a/apps/web/trpc/routers/calculate/types.ts +++ b/apps/web/trpc/routers/calculate/types.ts @@ -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; -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; + +export const CalculateOutputSchema = z + .object({ + data: OutputDataSchema, + success: z.boolean(), + }) + .or( + z.object({ + error: z.string(), + success: z.boolean(), + }) + ); + export type CalculateOutput = z.infer; - -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; - -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; - -const AdditionalDataSchema = z.object({ - maxAllAgencyPerc: z.number().nullable(), - maxCashflowMSFONominal: z.number().nullable(), - minCashflowMSFONominal: z.number().nullable(), -}); - -export type AdditionalData = z.infer; - -export const CalculateRequestSchema = z.object({ - additionalData: AdditionalDataSchema, - preparedPayments: PreparedPaymentSchema, - preparedValues: PreparedValuesSchema, -}); - -export type CalculateRequest = z.infer; diff --git a/apps/web/trpc/routers/calculate/validation.ts b/apps/web/trpc/routers/calculate/validation.ts index 6632519..0eac005 100644 --- a/apps/web/trpc/routers/calculate/validation.ts +++ b/apps/web/trpc/routers/calculate/validation.ts @@ -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; - -export async function validate({ - input, - context, -}: { - context: Context; - input: CalculateInput; -}): Promise { +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: '', }; } diff --git a/packages/tools/number.ts b/packages/tools/number.ts index 1ca4eb0..d6fc194 100644 --- a/packages/tools/number.ts +++ b/packages/tools/number.ts @@ -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)); }