Compare commits
27 Commits
dev
...
migration/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf7189123e | ||
|
|
e0de887c45 | ||
|
|
9b9e27bd74 | ||
|
|
4749c2167f | ||
|
|
de26e39eff | ||
|
|
600bb1a730 | ||
|
|
073dd1ac01 | ||
|
|
310736f050 | ||
|
|
333fcbd8b4 | ||
|
|
ca8d060851 | ||
|
|
9cac288dcc | ||
|
|
4acbcde1cc | ||
|
|
5a25b342f6 | ||
|
|
ab89ca3475 | ||
|
|
3cb0a6a73c | ||
|
|
2d2d5d6cc3 | ||
|
|
adf186d8da | ||
|
|
6107e0c15f | ||
|
|
8f47cc44f8 | ||
|
|
c8afffe5b2 | ||
|
|
2b6960de45 | ||
|
|
326257cd7c | ||
|
|
375e59fb6a | ||
|
|
9bedfe8fab | ||
|
|
7c40e2cf4f | ||
|
|
048da7947a | ||
|
|
eb7dc997e4 |
45
Components/Calculation/Form/Payments/PaymentsParams.jsx
Normal file
45
Components/Calculation/Form/Payments/PaymentsParams.jsx
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { observer } from 'mobx-react-lite';
|
||||||
|
import { useStore } from 'stores/hooks';
|
||||||
|
import { Flex } from 'UIKit/grid';
|
||||||
|
import elementsRender from '../../config/elements-render';
|
||||||
|
import { elements } from './config';
|
||||||
|
|
||||||
|
function PaymentsParams() {
|
||||||
|
const renderedElements = elements.map((elementName) => {
|
||||||
|
const render = elementsRender[elementName]?.render;
|
||||||
|
|
||||||
|
return render();
|
||||||
|
});
|
||||||
|
|
||||||
|
const [selectSeasonType, tbxParmentsDecreasePercent, selectHighSeasonStart] = renderedElements;
|
||||||
|
|
||||||
|
const { $calculation } = useStore();
|
||||||
|
const graphType = $calculation.getElementValue('radioGraphType');
|
||||||
|
|
||||||
|
switch (graphType) {
|
||||||
|
case 100_000_000:
|
||||||
|
return null;
|
||||||
|
case 100_000_001: {
|
||||||
|
return <Flex flexDirection="column">{selectSeasonType}</Flex>;
|
||||||
|
}
|
||||||
|
case 100_000_002: {
|
||||||
|
return <Flex flexDirection="column">{tbxParmentsDecreasePercent}</Flex>;
|
||||||
|
}
|
||||||
|
case 100_000_003: {
|
||||||
|
return (
|
||||||
|
<Flex flexDirection="column">
|
||||||
|
{selectSeasonType}
|
||||||
|
{selectHighSeasonStart}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case 100_000_004: {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default observer(PaymentsParams);
|
||||||
@ -4,7 +4,6 @@ export const id = 'payments';
|
|||||||
export const title = 'Платежи';
|
export const title = 'Платежи';
|
||||||
|
|
||||||
export const elements: ElementsRow['0'] = [
|
export const elements: ElementsRow['0'] = [
|
||||||
'radioGraphType',
|
|
||||||
'selectSeasonType',
|
'selectSeasonType',
|
||||||
'tbxParmentsDecreasePercent',
|
'tbxParmentsDecreasePercent',
|
||||||
'selectHighSeasonStart',
|
'selectHighSeasonStart',
|
||||||
|
|||||||
@ -1,18 +1,11 @@
|
|||||||
import { Box, Flex } from 'UIKit/grid';
|
import { Box, Flex } from 'UIKit/grid';
|
||||||
import elementsRender from '../../config/elements-render';
|
import elementsRender from '../../config/elements-render';
|
||||||
import { elements, id, title } from './config';
|
import { id, title } from './config';
|
||||||
|
import PaymentsParams from './PaymentsParams';
|
||||||
import PaymentsTable from './PaymentsTable';
|
import PaymentsTable from './PaymentsTable';
|
||||||
|
|
||||||
function Payments() {
|
function Payments() {
|
||||||
const renderedElements = elements.map((elementName) => {
|
const radioGraphType = elementsRender.radioGraphType.render();
|
||||||
const render = elementsRender[elementName]?.render;
|
|
||||||
|
|
||||||
return render();
|
|
||||||
});
|
|
||||||
|
|
||||||
// eslint-disable-next-line operator-linebreak
|
|
||||||
const [radioGraphType, selectSeasonType, tbxParmentsDecreasePercent, selectHighSeasonStart] =
|
|
||||||
renderedElements;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex flexDirection="column">
|
<Flex flexDirection="column">
|
||||||
@ -24,11 +17,7 @@ function Payments() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{radioGraphType}
|
{radioGraphType}
|
||||||
<Flex flexDirection="column">
|
<PaymentsParams />
|
||||||
{selectSeasonType}
|
|
||||||
{tbxParmentsDecreasePercent}
|
|
||||||
{selectHighSeasonStart}
|
|
||||||
</Flex>
|
|
||||||
</Box>
|
</Box>
|
||||||
<PaymentsTable />
|
<PaymentsTable />
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@ -2,7 +2,9 @@
|
|||||||
import { Container, Head } from 'Components/Layout/Element';
|
import { Container, Head } from 'Components/Layout/Element';
|
||||||
import Link from 'Elements/Link';
|
import Link from 'Elements/Link';
|
||||||
import Tooltip from 'Elements/Tooltip';
|
import Tooltip from 'Elements/Tooltip';
|
||||||
|
import { observer } from 'mobx-react-lite';
|
||||||
import type { ComponentProps } from 'react';
|
import type { ComponentProps } from 'react';
|
||||||
|
import { useStore } from 'stores/hooks';
|
||||||
import buildReadonly from '../../builders/build-readonly';
|
import buildReadonly from '../../builders/build-readonly';
|
||||||
import builders from '../elements-builders';
|
import builders from '../elements-builders';
|
||||||
import components from '../elements-components';
|
import components from '../elements-components';
|
||||||
@ -162,6 +164,42 @@ const overrideRender: Partial<Record<keyof typeof map, RenderProps>> = {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
selectSeasonType: {
|
||||||
|
render: () => {
|
||||||
|
const elementName = 'selectSeasonType';
|
||||||
|
const valueName = map.selectSeasonType;
|
||||||
|
const Component = components.selectSeasonType;
|
||||||
|
const props = elementsProps.selectSeasonType;
|
||||||
|
const builder = builders.selectSeasonType;
|
||||||
|
|
||||||
|
const Element = builder(Component, {
|
||||||
|
elementName,
|
||||||
|
valueName,
|
||||||
|
});
|
||||||
|
|
||||||
|
const Title = observer(() => {
|
||||||
|
const { $calculation } = useStore();
|
||||||
|
const graphType = $calculation.getElementValue('radioGraphType');
|
||||||
|
|
||||||
|
switch (graphType) {
|
||||||
|
case 100_000_001:
|
||||||
|
return <span>Тип дегрессии</span>;
|
||||||
|
case 100_000_003:
|
||||||
|
return <span>Тип сезонности</span>;
|
||||||
|
default:
|
||||||
|
return <span>{titles.selectSeasonType}</span>;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Head htmlFor={elementName} title={<Title />} />
|
||||||
|
<Element {...props} id={elementName} />
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default overrideRender;
|
export default overrideRender;
|
||||||
|
|||||||
@ -23,7 +23,7 @@ export function Head({
|
|||||||
addon,
|
addon,
|
||||||
htmlFor,
|
htmlFor,
|
||||||
}: {
|
}: {
|
||||||
title: string;
|
title: ReactNode;
|
||||||
addon?: ReactNode;
|
addon?: ReactNode;
|
||||||
htmlFor: string;
|
htmlFor: string;
|
||||||
}) {
|
}) {
|
||||||
|
|||||||
@ -1,6 +1,51 @@
|
|||||||
import { alphabetical } from 'radash/dist/array';
|
import { alphabetical } from 'radash/dist/array';
|
||||||
import type { CalculationOptions } from 'stores/calculation/options/types';
|
import type { CalculationOptions } from 'stores/calculation/options/types';
|
||||||
|
|
||||||
|
export const selectSeasonType = [
|
||||||
|
{
|
||||||
|
label: '6/6',
|
||||||
|
value: 100_000_000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '8/4',
|
||||||
|
value: 100_000_001,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '4/4/4',
|
||||||
|
value: 100_000_002,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Гибкая дегрессия',
|
||||||
|
value: 100_000_007,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '100.50.25',
|
||||||
|
value: 100_000_003,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '100.30.10',
|
||||||
|
value: 100_000_004,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '100.70.40',
|
||||||
|
value: 100_000_005,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '100.7.3',
|
||||||
|
value: 100_000_006,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const selectHighSeasonStart = Array.from(
|
||||||
|
{
|
||||||
|
length: 12,
|
||||||
|
},
|
||||||
|
(_, i) => ({
|
||||||
|
label: `${i + 2}`,
|
||||||
|
value: 100_000_000 + i,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
const defaultOptions: CalculationOptions = {
|
const defaultOptions: CalculationOptions = {
|
||||||
radioLastPaymentRule: [
|
radioLastPaymentRule: [
|
||||||
{
|
{
|
||||||
@ -51,50 +96,9 @@ const defaultOptions: CalculationOptions = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
selectSeasonType: [
|
selectSeasonType,
|
||||||
{
|
|
||||||
label: '6/6',
|
|
||||||
value: 100_000_000,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '8/4',
|
|
||||||
value: 100_000_001,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '4/4/4',
|
|
||||||
value: 100_000_002,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Гибкая дегрессия',
|
|
||||||
value: 100_000_007,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '100.50.25',
|
|
||||||
value: 100_000_003,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '100.30.10',
|
|
||||||
value: 100_000_004,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '100.70.40',
|
|
||||||
value: 100_000_005,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '100.7.3',
|
|
||||||
value: 100_000_006,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
selectHighSeasonStart: Array.from(
|
selectHighSeasonStart,
|
||||||
{
|
|
||||||
length: 12,
|
|
||||||
},
|
|
||||||
(_, i) => ({
|
|
||||||
label: `${i + 2}`,
|
|
||||||
value: 100_000_000 + i,
|
|
||||||
})
|
|
||||||
),
|
|
||||||
|
|
||||||
radioDeliveryTime: [
|
radioDeliveryTime: [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -23,8 +23,8 @@ const defaultValues: CalculationValues = {
|
|||||||
balanceHolder: 100_000_001,
|
balanceHolder: 100_000_001,
|
||||||
graphType: 100_000_000,
|
graphType: 100_000_000,
|
||||||
parmentsDecreasePercent: 94,
|
parmentsDecreasePercent: 94,
|
||||||
seasonType: 100_000_000,
|
seasonType: null,
|
||||||
highSeasonStart: 100_000_000,
|
highSeasonStart: null,
|
||||||
comissionPerc: 0,
|
comissionPerc: 0,
|
||||||
comissionRub: 0,
|
comissionRub: 0,
|
||||||
saleBonus: 1.3,
|
saleBonus: 1.3,
|
||||||
|
|||||||
@ -26,9 +26,7 @@ export default function validationReactions(
|
|||||||
({ hasPaymentsErrors, finGAPInsuranceCompany }) => {
|
({ hasPaymentsErrors, finGAPInsuranceCompany }) => {
|
||||||
if (finGAPInsuranceCompany && hasPaymentsErrors) {
|
if (finGAPInsuranceCompany && hasPaymentsErrors) {
|
||||||
removeError = $tables.fingap.validation.addError(errorText);
|
removeError = $tables.fingap.validation.addError(errorText);
|
||||||
} else {
|
} else if (removeError) removeError();
|
||||||
removeError();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fireImmediately: true,
|
fireImmediately: true,
|
||||||
|
|||||||
25
process/payments/lib/__tests__/seasons.test.js
Normal file
25
process/payments/lib/__tests__/seasons.test.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { getPositionIndex } from '../seasons-tools';
|
||||||
|
|
||||||
|
describe('process/payments/lib/seasons-tools', () => {
|
||||||
|
describe('[function] getPositionIndex', () => {
|
||||||
|
test('should return 0', () => {
|
||||||
|
const result = getPositionIndex(100_000_001, 0);
|
||||||
|
expect(result).toEqual(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return 0', () => {
|
||||||
|
const result = getPositionIndex(100_000_001, 7);
|
||||||
|
expect(result).toEqual(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return 1', () => {
|
||||||
|
const result = getPositionIndex(100_000_001, 8);
|
||||||
|
expect(result).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return 1', () => {
|
||||||
|
const result = getPositionIndex(100_000_001, 11);
|
||||||
|
expect(result).toEqual(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
15
process/payments/lib/seasons-constants.ts
Normal file
15
process/payments/lib/seasons-constants.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
export const SEASONS_PERIODS: Record<number, Array<number>> = {
|
||||||
|
100_000_000: [0, 6],
|
||||||
|
100_000_001: [0, 8],
|
||||||
|
100_000_002: [0, 4, 8],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FORBIDDEN_HIGH_SEASON_START: Record<number, Array<string>> = {
|
||||||
|
100_000_000: ['9', '10'],
|
||||||
|
100_000_001: ['7', '8', '9'],
|
||||||
|
100_000_002: ['11'],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SEASONS_PERIOD_NUMBER = 12;
|
||||||
|
|
||||||
|
export const DEFAULT_SEASONS_VALUES = [100, 75, 50];
|
||||||
55
process/payments/lib/seasons-tools.ts
Normal file
55
process/payments/lib/seasons-tools.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
/* eslint-disable implicit-arrow-linebreak */
|
||||||
|
import type { CalculationValues } from 'stores/calculation/values/types';
|
||||||
|
import { SEASONS_PERIODS, SEASONS_PERIOD_NUMBER } from './seasons-constants';
|
||||||
|
|
||||||
|
type SeasonType = NonNullable<CalculationValues['seasonType']>;
|
||||||
|
type LeasingPeriod = CalculationValues['leasingPeriod'];
|
||||||
|
|
||||||
|
export function getPositionIndex(seasonType: SeasonType, seasonsIndex: number) {
|
||||||
|
let positionIndex = 0;
|
||||||
|
|
||||||
|
SEASONS_PERIODS[seasonType].forEach((position, i) => {
|
||||||
|
if (seasonsIndex >= position) {
|
||||||
|
positionIndex = i;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return positionIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSeasonsValues(seasonType: SeasonType, seasons: Array<number>) {
|
||||||
|
return SEASONS_PERIODS[seasonType].map((index) => seasons[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateSeasons(seasonType: SeasonType, values: Array<number>) {
|
||||||
|
const positions = SEASONS_PERIODS[seasonType];
|
||||||
|
|
||||||
|
const seasons: Array<number> = [];
|
||||||
|
|
||||||
|
[...positions, SEASONS_PERIOD_NUMBER].forEach((position, i, arr) => {
|
||||||
|
if (position < SEASONS_PERIOD_NUMBER) {
|
||||||
|
const start = arr[i];
|
||||||
|
const end = arr[i + 1];
|
||||||
|
const value = values[i];
|
||||||
|
|
||||||
|
// eslint-disable-next-line unicorn/new-for-builtins
|
||||||
|
seasons.push(...Array(end - start).fill(value));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return seasons;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateSeasonsPayments(leasingPeriod: LeasingPeriod, seasons: Array<number>) {
|
||||||
|
return (
|
||||||
|
Array.from(
|
||||||
|
{
|
||||||
|
length: Math.floor((leasingPeriod - 2) / SEASONS_PERIOD_NUMBER),
|
||||||
|
},
|
||||||
|
() => seasons
|
||||||
|
)
|
||||||
|
.flat()
|
||||||
|
// eslint-disable-next-line unicorn/prefer-spread
|
||||||
|
.concat(seasons.slice(0, (leasingPeriod - 2) % SEASONS_PERIOD_NUMBER))
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,25 +1,78 @@
|
|||||||
|
/* eslint-disable function-paren-newline */
|
||||||
|
/* eslint-disable implicit-arrow-linebreak */
|
||||||
import type { ApolloClient } from '@apollo/client';
|
import type { ApolloClient } from '@apollo/client';
|
||||||
import { reaction, toJS } from 'mobx';
|
import type { QueryClient } from '@tanstack/react-query';
|
||||||
|
import { selectHighSeasonStart, selectSeasonType } from 'config/default-options';
|
||||||
|
import { comparer, reaction, toJS } from 'mobx';
|
||||||
|
import type { CalculationOptions } from 'stores/calculation/options/types';
|
||||||
import type RootStore from 'stores/root';
|
import type RootStore from 'stores/root';
|
||||||
|
import type { Row } from 'stores/tables/payments/types';
|
||||||
|
import { difference, shift } from 'tools/array';
|
||||||
|
import * as seasonsConstants from './lib/seasons-constants';
|
||||||
|
import * as seasonsTools from './lib/seasons-tools';
|
||||||
|
import validatePaymentsTable from './validation';
|
||||||
|
|
||||||
export default function paymentsReactions(store: RootStore, apolloClient: ApolloClient<object>) {
|
export default function paymentsReactions(
|
||||||
|
store: RootStore,
|
||||||
|
apolloClient: ApolloClient<object>,
|
||||||
|
queryClient: QueryClient
|
||||||
|
) {
|
||||||
const { $calculation, $tables } = store;
|
const { $calculation, $tables } = store;
|
||||||
|
|
||||||
|
reaction(
|
||||||
|
() => $calculation.getElementValue('tbxFirstPaymentPerc'),
|
||||||
|
(firstPaymentPerc) => {
|
||||||
|
$tables.payments.setValue(0, firstPaymentPerc);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
reaction(
|
||||||
|
() => $calculation.getElementValue('tbxLastPaymentPerc'),
|
||||||
|
(lastPaymentPerc) => {
|
||||||
|
const paymentsLength = $tables.payments.values.length;
|
||||||
|
$tables.payments.setValue(paymentsLength - 1, lastPaymentPerc);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* При изменении срока лизинга регулируем длину таблицы платежей
|
* Аннуитет
|
||||||
*/
|
*/
|
||||||
reaction(
|
reaction(
|
||||||
() => $calculation.$values.getValue('leasingPeriod'),
|
() => {
|
||||||
(leasingPeriod) => {
|
const graphType = $calculation.getElementValue('radioGraphType');
|
||||||
if (leasingPeriod) {
|
const leasingPeriod = $calculation.getElementValue('tbxLeasingPeriod');
|
||||||
$tables.payments.setValues(
|
|
||||||
Array.from(
|
return {
|
||||||
{
|
graphType,
|
||||||
length: leasingPeriod,
|
leasingPeriod,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
() => 0
|
({ graphType, leasingPeriod }) => {
|
||||||
)
|
if (graphType === 100_000_000) {
|
||||||
|
const middlePayments: Row[] = Array.from(
|
||||||
|
{
|
||||||
|
length: leasingPeriod - 2,
|
||||||
|
},
|
||||||
|
() => ({
|
||||||
|
value: 100,
|
||||||
|
status: 'Disabled',
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const firstPaymentPerc = $calculation.getElementValue('tbxFirstPaymentPerc');
|
||||||
|
const lastPaymentPerc = $calculation.getElementValue('tbxLastPaymentPerc');
|
||||||
|
|
||||||
|
$tables.payments.setRows([
|
||||||
|
{
|
||||||
|
value: firstPaymentPerc,
|
||||||
|
status: 'Disabled',
|
||||||
|
},
|
||||||
|
...middlePayments,
|
||||||
|
{
|
||||||
|
value: lastPaymentPerc,
|
||||||
|
status: 'Disabled',
|
||||||
|
},
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -28,22 +81,479 @@ export default function paymentsReactions(store: RootStore, apolloClient: Apollo
|
|||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Проверяем платежи на 0
|
* Равноубывающий
|
||||||
|
*/
|
||||||
|
reaction(
|
||||||
|
() => {
|
||||||
|
const graphType = $calculation.getElementValue('radioGraphType');
|
||||||
|
const leasingPeriod = $calculation.getElementValue('tbxLeasingPeriod');
|
||||||
|
const parmentsDecreasePercent = $calculation.getElementValue('tbxParmentsDecreasePercent');
|
||||||
|
|
||||||
|
return {
|
||||||
|
graphType,
|
||||||
|
leasingPeriod,
|
||||||
|
parmentsDecreasePercent,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
({ graphType, leasingPeriod, parmentsDecreasePercent }) => {
|
||||||
|
if (graphType === 100_000_002) {
|
||||||
|
const middlePayments: Row[] = Array.from(
|
||||||
|
{
|
||||||
|
length: leasingPeriod - 2,
|
||||||
|
},
|
||||||
|
(_, k) => {
|
||||||
|
const payment = 100 * (parmentsDecreasePercent / 100) ** k;
|
||||||
|
|
||||||
|
return {
|
||||||
|
value: Number(payment.toFixed(2)),
|
||||||
|
status: 'Disabled',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const firstPaymentPerc = $calculation.getElementValue('tbxFirstPaymentPerc');
|
||||||
|
const lastPaymentPerc = $calculation.getElementValue('tbxLastPaymentPerc');
|
||||||
|
|
||||||
|
$tables.payments.setRows([
|
||||||
|
{
|
||||||
|
value: firstPaymentPerc,
|
||||||
|
status: 'Disabled',
|
||||||
|
},
|
||||||
|
...middlePayments,
|
||||||
|
{
|
||||||
|
value: lastPaymentPerc,
|
||||||
|
status: 'Disabled',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Легкий старт
|
||||||
|
*/
|
||||||
|
reaction(
|
||||||
|
() => {
|
||||||
|
const graphType = $calculation.getElementValue('radioGraphType');
|
||||||
|
const leasingPeriod = $calculation.getElementValue('tbxLeasingPeriod');
|
||||||
|
|
||||||
|
return {
|
||||||
|
graphType,
|
||||||
|
leasingPeriod,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
({ graphType, leasingPeriod }) => {
|
||||||
|
if (graphType === 100_000_004) {
|
||||||
|
const editablePayments: Row[] = [
|
||||||
|
{
|
||||||
|
value: 25,
|
||||||
|
status: 'Default',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 50,
|
||||||
|
status: 'Default',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 75,
|
||||||
|
status: 'Default',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const payments: Row[] = Array.from(
|
||||||
|
{
|
||||||
|
length: leasingPeriod - 5,
|
||||||
|
},
|
||||||
|
() => ({
|
||||||
|
value: 100,
|
||||||
|
status: 'Disabled',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const firstPaymentPerc = $calculation.getElementValue('tbxFirstPaymentPerc');
|
||||||
|
const lastPaymentPerc = $calculation.getElementValue('tbxLastPaymentPerc');
|
||||||
|
|
||||||
|
$tables.payments.setRows([
|
||||||
|
{
|
||||||
|
value: firstPaymentPerc,
|
||||||
|
status: 'Disabled',
|
||||||
|
},
|
||||||
|
...editablePayments,
|
||||||
|
...payments,
|
||||||
|
{
|
||||||
|
value: lastPaymentPerc,
|
||||||
|
status: 'Disabled',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Дегрессия
|
||||||
|
*/
|
||||||
|
// TODO: const exeption = tarif?.evo_seasons_type_exception
|
||||||
|
const degressionSeasonTypes = new Set([
|
||||||
|
100_000_003, 100_000_004, 100_000_005, 100_000_006, 100_000_007,
|
||||||
|
]);
|
||||||
|
const seasonSeasonTypes = new Set([100_000_000, 100_000_001, 100_000_002]);
|
||||||
|
|
||||||
|
reaction(
|
||||||
|
() => {
|
||||||
|
const graphType = $calculation.getElementValue('radioGraphType');
|
||||||
|
|
||||||
|
return graphType;
|
||||||
|
},
|
||||||
|
(graphType) => {
|
||||||
|
let selectSeasonTypeOptions: CalculationOptions['selectSeasonType'] = [];
|
||||||
|
|
||||||
|
if (graphType === 100_000_001) {
|
||||||
|
selectSeasonTypeOptions = selectSeasonType.filter((option) =>
|
||||||
|
degressionSeasonTypes.has(option.value)
|
||||||
|
);
|
||||||
|
} else if (graphType === 100_000_003) {
|
||||||
|
selectSeasonTypeOptions = selectSeasonType.filter((option) =>
|
||||||
|
seasonSeasonTypes.has(option.value)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$calculation.setElementOptions('selectSeasonType', selectSeasonTypeOptions);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const degressionSteps: { [key: number]: Array<number> } = {
|
||||||
|
100_000_003: [100, 50, 25],
|
||||||
|
100_000_004: [100, 30, 10],
|
||||||
|
100_000_005: [100, 70, 40],
|
||||||
|
100_000_006: [100, 7, 3],
|
||||||
|
};
|
||||||
|
|
||||||
|
reaction(
|
||||||
|
() => {
|
||||||
|
const degressionType = $calculation.getElementValue('selectSeasonType');
|
||||||
|
const leasingPeriod = $calculation.getElementValue('tbxLeasingPeriod');
|
||||||
|
const graphType = $calculation.getElementValue('radioGraphType');
|
||||||
|
|
||||||
|
return {
|
||||||
|
degressionType,
|
||||||
|
leasingPeriod,
|
||||||
|
graphType,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
({ degressionType, leasingPeriod, graphType }) => {
|
||||||
|
if (graphType === 100_000_001) {
|
||||||
|
let payments: Row[] = [];
|
||||||
|
|
||||||
|
switch (degressionType) {
|
||||||
|
case 100_000_007: {
|
||||||
|
const editablePayments: Row[] = Array.from(
|
||||||
|
{
|
||||||
|
length: leasingPeriod - 3,
|
||||||
|
},
|
||||||
|
() => ({
|
||||||
|
value: 100,
|
||||||
|
status: 'Default',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
payments = [
|
||||||
|
{
|
||||||
|
value: 100,
|
||||||
|
status: 'Disabled',
|
||||||
|
},
|
||||||
|
...editablePayments,
|
||||||
|
];
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 100_000_003:
|
||||||
|
case 100_000_004:
|
||||||
|
case 100_000_005:
|
||||||
|
case 100_000_006: {
|
||||||
|
const [step1, step2, step3] = degressionSteps[degressionType];
|
||||||
|
const paymentsInStep = Math.ceil((leasingPeriod - 2) / 3);
|
||||||
|
|
||||||
|
payments = Array.from(
|
||||||
|
{
|
||||||
|
length: leasingPeriod - 2,
|
||||||
|
},
|
||||||
|
(_v, i) => {
|
||||||
|
let value = step3;
|
||||||
|
|
||||||
|
if (i <= paymentsInStep * 2 - 1) {
|
||||||
|
value = step2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i <= paymentsInStep - 1) {
|
||||||
|
value = step1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
value,
|
||||||
|
status: 'Disabled',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstPaymentPerc = $calculation.getElementValue('tbxFirstPaymentPerc');
|
||||||
|
const lastPaymentPerc = $calculation.getElementValue('tbxLastPaymentPerc');
|
||||||
|
|
||||||
|
$tables.payments.setRows([
|
||||||
|
{
|
||||||
|
value: firstPaymentPerc,
|
||||||
|
status: 'Disabled',
|
||||||
|
},
|
||||||
|
...payments,
|
||||||
|
{
|
||||||
|
value: lastPaymentPerc,
|
||||||
|
status: 'Disabled',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
reaction(
|
||||||
|
() => {
|
||||||
|
const graphType = $calculation.getElementValue('radioGraphType');
|
||||||
|
const payments = toJS($tables.payments.values);
|
||||||
|
const degressionType = $calculation.getElementValue('selectSeasonType');
|
||||||
|
|
||||||
|
return {
|
||||||
|
graphType,
|
||||||
|
payments,
|
||||||
|
degressionType,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
(nextParams, prevParams) => {
|
||||||
|
if (nextParams.graphType === 100_000_001 && nextParams.degressionType === 100_000_007) {
|
||||||
|
const changes = difference(nextParams.payments, prevParams.payments);
|
||||||
|
|
||||||
|
if (changes === null || changes.length > 1) return;
|
||||||
|
|
||||||
|
const [changeIndex] = changes;
|
||||||
|
const value = nextParams.payments[changeIndex];
|
||||||
|
const payments = nextParams.payments.slice(1, -1).map((payment, i) => {
|
||||||
|
if (i <= changeIndex - 2) return payment;
|
||||||
|
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
|
||||||
|
const firstPaymentPerc = $calculation.getElementValue('tbxFirstPaymentPerc');
|
||||||
|
const lastPaymentPerc = $calculation.getElementValue('tbxLastPaymentPerc');
|
||||||
|
|
||||||
|
$tables.payments.setValues([firstPaymentPerc, ...payments, lastPaymentPerc]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
delay: 50,
|
||||||
|
equals: comparer.structural,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Сезонный
|
||||||
|
*/
|
||||||
|
|
||||||
|
reaction(
|
||||||
|
() => {
|
||||||
|
const graphType = $calculation.getElementValue('radioGraphType');
|
||||||
|
|
||||||
|
return {
|
||||||
|
graphType,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
({ graphType }) => {
|
||||||
|
if (graphType !== 100_000_003) return;
|
||||||
|
|
||||||
|
const seasonType = $calculation.getElementValue('selectSeasonType');
|
||||||
|
const highSeasonStart = $calculation.getElementValue('selectHighSeasonStart');
|
||||||
|
|
||||||
|
if (!seasonType || !highSeasonStart) {
|
||||||
|
$tables.payments.setValues([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
reaction(
|
||||||
|
() => {
|
||||||
|
const seasonType = $calculation.getElementValue('selectSeasonType');
|
||||||
|
|
||||||
|
return seasonType;
|
||||||
|
},
|
||||||
|
(seasonType) => {
|
||||||
|
const graphType = $calculation.getElementValue('radioGraphType');
|
||||||
|
if (graphType !== 100_000_003) return;
|
||||||
|
|
||||||
|
if (!seasonType) {
|
||||||
|
$calculation.resetElement('selectHighSeasonStart');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const highSeasonStartOptions = selectHighSeasonStart.filter(
|
||||||
|
(option) => !seasonsConstants.FORBIDDEN_HIGH_SEASON_START[seasonType].includes(option.label)
|
||||||
|
);
|
||||||
|
|
||||||
|
$calculation.setElementOptions('selectHighSeasonStart', highSeasonStartOptions);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
function generateSeasonPaymentsRows(seasonType: number, shiftNumber: number, payments: number[]) {
|
||||||
|
const shiftedPeriods = new Set(
|
||||||
|
seasonsConstants.SEASONS_PERIODS[seasonType].map(
|
||||||
|
(position) => (position + shiftNumber) % seasonsConstants.SEASONS_PERIOD_NUMBER
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const rows: Row[] = payments.map((value, i) => ({
|
||||||
|
value,
|
||||||
|
status: shiftedPeriods.has(i) ? 'Default' : 'Disabled',
|
||||||
|
}));
|
||||||
|
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
reaction(
|
||||||
|
() => {
|
||||||
|
const seasonType = $calculation.getElementValue('selectSeasonType');
|
||||||
|
const highSeasonStartOption = $calculation.getElementOption('selectHighSeasonStart');
|
||||||
|
const leasingPeriod = $calculation.getElementValue('tbxLeasingPeriod');
|
||||||
|
|
||||||
|
return {
|
||||||
|
seasonType,
|
||||||
|
highSeasonStartOption,
|
||||||
|
leasingPeriod,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
({ seasonType, highSeasonStartOption, leasingPeriod }) => {
|
||||||
|
const graphType = $calculation.getElementValue('radioGraphType');
|
||||||
|
if (graphType !== 100_000_003) return;
|
||||||
|
|
||||||
|
if (!seasonType || !highSeasonStartOption) {
|
||||||
|
$tables.payments.setValues([]);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const seasons = seasonsTools.generateSeasons(
|
||||||
|
seasonType,
|
||||||
|
seasonsConstants.DEFAULT_SEASONS_VALUES
|
||||||
|
);
|
||||||
|
const shiftNumber = Number.parseInt(highSeasonStartOption.label, 10) - 2;
|
||||||
|
const payments = seasonsTools.generateSeasonsPayments(
|
||||||
|
leasingPeriod,
|
||||||
|
shift(seasons, shiftNumber)
|
||||||
|
);
|
||||||
|
const rows: Row[] = generateSeasonPaymentsRows(seasonType, shiftNumber, payments);
|
||||||
|
const firstPaymentPerc = $calculation.getElementValue('tbxFirstPaymentPerc');
|
||||||
|
const lastPaymentPerc = $calculation.getElementValue('tbxLastPaymentPerc');
|
||||||
|
|
||||||
|
$tables.payments.setRows([
|
||||||
|
{
|
||||||
|
value: firstPaymentPerc,
|
||||||
|
status: 'Disabled',
|
||||||
|
},
|
||||||
|
...rows,
|
||||||
|
{
|
||||||
|
value: lastPaymentPerc,
|
||||||
|
status: 'Disabled',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
reaction(
|
||||||
|
() => {
|
||||||
|
const payments = toJS($tables.payments.values);
|
||||||
|
const seasons = payments.slice(1, seasonsConstants.SEASONS_PERIOD_NUMBER + 1);
|
||||||
|
|
||||||
|
return seasons;
|
||||||
|
},
|
||||||
|
(nextSeasons, prevSeasons) => {
|
||||||
|
const graphType = $calculation.getElementValue('radioGraphType');
|
||||||
|
if (graphType !== 100_000_003) return;
|
||||||
|
|
||||||
|
const seasonType = $calculation.getElementValue('selectSeasonType');
|
||||||
|
const highSeasonStartOption = $calculation.getElementOption('selectHighSeasonStart');
|
||||||
|
if (!seasonType || !highSeasonStartOption) return;
|
||||||
|
|
||||||
|
const shiftNumber = Number.parseInt(highSeasonStartOption.label, 10) - 2;
|
||||||
|
const unshiftedNextSeasons = shift(nextSeasons, -shiftNumber);
|
||||||
|
const unshiftedPrevSeasons = shift(prevSeasons, -shiftNumber);
|
||||||
|
|
||||||
|
const changes = difference(unshiftedNextSeasons, unshiftedPrevSeasons);
|
||||||
|
if (changes === null || changes.length > 1) return;
|
||||||
|
|
||||||
|
const [changeIndex] = changes;
|
||||||
|
const positionIndex = seasonsTools.getPositionIndex(seasonType, changeIndex);
|
||||||
|
|
||||||
|
const values = seasonsTools.getSeasonsValues(seasonType, unshiftedNextSeasons);
|
||||||
|
values[positionIndex] = unshiftedNextSeasons[changeIndex];
|
||||||
|
|
||||||
|
const seasons = seasonsTools.generateSeasons(seasonType, values);
|
||||||
|
const leasingPeriod = $calculation.getElementValue('tbxLeasingPeriod');
|
||||||
|
const payments = seasonsTools.generateSeasonsPayments(
|
||||||
|
leasingPeriod,
|
||||||
|
shift(seasons, shiftNumber)
|
||||||
|
);
|
||||||
|
const rows: Row[] = generateSeasonPaymentsRows(seasonType, shiftNumber, payments);
|
||||||
|
const firstPaymentPerc = $calculation.getElementValue('tbxFirstPaymentPerc');
|
||||||
|
const lastPaymentPerc = $calculation.getElementValue('tbxLastPaymentPerc');
|
||||||
|
|
||||||
|
$tables.payments.setRows([
|
||||||
|
{
|
||||||
|
value: firstPaymentPerc,
|
||||||
|
status: 'Disabled',
|
||||||
|
},
|
||||||
|
...rows,
|
||||||
|
{
|
||||||
|
value: lastPaymentPerc,
|
||||||
|
status: 'Disabled',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
delay: 50,
|
||||||
|
equals: comparer.structural,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Валидация
|
||||||
*/
|
*/
|
||||||
const errorText = 'Значения должны быть больше 0';
|
|
||||||
let removeError: () => void;
|
let removeError: () => void;
|
||||||
|
|
||||||
reaction(
|
reaction(
|
||||||
() => toJS($tables.payments.values),
|
() => {
|
||||||
(values) => {
|
const payments = toJS($tables.payments.values);
|
||||||
if (values.includes(0)) {
|
const graphType = $calculation.getElementValue('radioGraphType');
|
||||||
|
const seasonType = $calculation.getElementValue('selectSeasonType');
|
||||||
|
const highSeasonStart = $calculation.getElementValue('selectHighSeasonStart');
|
||||||
|
const leasingPeriod = $calculation.getElementValue('tbxLeasingPeriod');
|
||||||
|
|
||||||
|
return {
|
||||||
|
payments,
|
||||||
|
graphType,
|
||||||
|
seasonType,
|
||||||
|
highSeasonStart,
|
||||||
|
leasingPeriod,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
if (removeError) removeError();
|
||||||
|
const errorText = validatePaymentsTable(store);
|
||||||
|
|
||||||
|
if (errorText) {
|
||||||
removeError = $tables.payments.validation.addError(errorText);
|
removeError = $tables.payments.validation.addError(errorText);
|
||||||
} else {
|
|
||||||
removeError();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fireImmediately: true,
|
delay: 50,
|
||||||
|
equals: comparer.structural,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
134
process/payments/validation.ts
Normal file
134
process/payments/validation.ts
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
/* eslint-disable max-len */
|
||||||
|
/* eslint-disable no-case-declarations */
|
||||||
|
import { counting, max, min, sort } from 'radash/dist/array';
|
||||||
|
import type RootStore from 'stores/root';
|
||||||
|
import { areEqual, isSorted, shift } from 'tools/array';
|
||||||
|
import { SEASONS_PERIODS, SEASONS_PERIOD_NUMBER } from './lib/seasons-constants';
|
||||||
|
|
||||||
|
export default function validatePaymentsTable({ $calculation, $tables }: RootStore) {
|
||||||
|
switch ($calculation.getElementValue('radioGraphType')) {
|
||||||
|
// Дегрессия
|
||||||
|
case 100_000_001:
|
||||||
|
if (!$calculation.getElementValue('selectSeasonType')) {
|
||||||
|
return 'Не выбран тип дегрессии';
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* в таблице платежей в столбце Соотношение платежей для строк с 2 до "Срок лизинга-1" минимальное значение должно быть равно 3
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
const MIN_PAYMENT = 3;
|
||||||
|
const leasingPeriod = $calculation.getElementValue('tbxLeasingPeriod');
|
||||||
|
const targetPayments = $tables.payments.values.slice(1, leasingPeriod - 1);
|
||||||
|
|
||||||
|
if (!targetPayments.every((payment) => payment >= MIN_PAYMENT)) {
|
||||||
|
return `Минимальное значение платежа должно быть равно ${MIN_PAYMENT}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* при Дегрессии все значения не должны быть равны друг другу + что при Легком старте 2,3 и 4 платежи не должны быть равны 100
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
const leasingPeriod = $calculation.getElementValue('tbxLeasingPeriod');
|
||||||
|
const targetPayments = $tables.payments.values.slice(1, leasingPeriod - 1);
|
||||||
|
|
||||||
|
if (new Set(targetPayments).size === 1) {
|
||||||
|
return 'Платежи не должны быть равны друг другу';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверка на возрастание
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
const leasingPeriod = $calculation.getElementValue('tbxLeasingPeriod');
|
||||||
|
const targetPayments = $tables.payments.values.slice(1, leasingPeriod - 1);
|
||||||
|
|
||||||
|
for (let i = 2; i < targetPayments.length - 1; i += 1) {
|
||||||
|
if (targetPayments[i] > targetPayments[i - 1]) {
|
||||||
|
return 'Платежи должны убывать';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Если вид графика = Дегрессия И значения в "Соотношении платежей" у 2, 3 и 4 платежа отличаются друг от друга не более чем на 10
|
||||||
|
* (т.е. берем значения в этих полях, определяем максимальное и минимальное значение и смотрим на их разницу)
|
||||||
|
* то не осуществлять Расчет графика и выводить ошибку "Нельзя осуществить расчет - указана очень жетская дегрессия.
|
||||||
|
* На 2-4 платежах Соотношение платежа должен отличаться не более чем на 10%",
|
||||||
|
* иначе осуществлять расчет
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
const targetPayments = $tables.payments.values.slice(1, 4);
|
||||||
|
|
||||||
|
if (max(targetPayments) - min(targetPayments) > 10) {
|
||||||
|
return 'Указана очень жесткая дегрессия';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Если вид графика = Дегрессия И значения в "Соотношении платежей" для строк с 2 До "Срок лизинга-1" как минимум 2 раза по 2 платежа должны между собой быть равны
|
||||||
|
* (т.е. берем значения "Соотношения платежей" для строк с 2 до "Срок лизинга-1" и делаем сводную таблицу - если кол-во одинаковых значение больше 2 встречаются 2 и более раза),
|
||||||
|
* то осуществлять расчет,
|
||||||
|
* иначе не осуществлять Расчет графика и выводить ошибку "Нельзя осуществить расчет - указана очень жетская дегрессия. Не менее чем у 4х платежей "Соотношение платежа" должно не отличаться между самой",
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
const leasingPeriod = $calculation.getElementValue('tbxLeasingPeriod');
|
||||||
|
const targetPayments = $tables.payments.values.slice(1, leasingPeriod - 1);
|
||||||
|
const counts = counting(targetPayments, (v) => v);
|
||||||
|
if (Object.values(counts).filter((count) => count > 1).length < 2) {
|
||||||
|
return 'Указана очень жесткая дегрессия. Не менее чем у 4х платежей соотношение должно не отличаться между собой';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 100_000_003: {
|
||||||
|
const leasingPeriod = $calculation.getElementValue('tbxLeasingPeriod');
|
||||||
|
if (leasingPeriod < 14) {
|
||||||
|
return 'При сезонном виде графика срок лизинга должен быть больше 14 месяцев';
|
||||||
|
}
|
||||||
|
|
||||||
|
const seasonType = $calculation.getElementValue('selectSeasonType');
|
||||||
|
if (!seasonType) {
|
||||||
|
return 'Не выбран тип сезонности';
|
||||||
|
}
|
||||||
|
|
||||||
|
const highSeasonStartOption = $calculation.getElementOption('selectHighSeasonStart');
|
||||||
|
if (!highSeasonStartOption) {
|
||||||
|
return 'Не выбрано смещение сезонности';
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const seasons = $tables.payments.values.slice(1, SEASONS_PERIOD_NUMBER + 1);
|
||||||
|
const shiftNumber = Number.parseInt(highSeasonStartOption.label, 10) - 2;
|
||||||
|
const unshiftedSeasons = shift(seasons, -shiftNumber);
|
||||||
|
|
||||||
|
const positions = SEASONS_PERIODS[seasonType];
|
||||||
|
const seasonsValues = positions.map((position) => unshiftedSeasons[position]);
|
||||||
|
|
||||||
|
if (isSorted(seasonsValues)) {
|
||||||
|
return 'Сезонные платежи должны убывать';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Легкий старт
|
||||||
|
case 100_000_004: {
|
||||||
|
const targetPayments = $tables.payments.values.slice(1, 4);
|
||||||
|
const sortedPayments = sort(targetPayments, (x) => x);
|
||||||
|
const areEqualPayments = new Set(targetPayments).size === 1;
|
||||||
|
|
||||||
|
if (!areEqual(targetPayments, sortedPayments) || areEqualPayments) {
|
||||||
|
return '2, 3, 4 платежи должны возрастать';
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
@ -34,7 +34,7 @@ export default class OptionsStore {
|
|||||||
if (
|
if (
|
||||||
// eslint-disable-next-line operator-linebreak
|
// eslint-disable-next-line operator-linebreak
|
||||||
!this.options[elementName]?.length ||
|
!this.options[elementName]?.length ||
|
||||||
this.options[elementName].some((x) => x.value === value)
|
!this.options[elementName].some((x) => x.value === value)
|
||||||
) {
|
) {
|
||||||
this.root.$calculation.resetElementValue(elementName);
|
this.root.$calculation.resetElementValue(elementName);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,11 +4,7 @@ import { makeAutoObservable, observable, reaction } from 'mobx';
|
|||||||
|
|
||||||
import type RootStore from 'stores/root';
|
import type RootStore from 'stores/root';
|
||||||
import Validation from '../validation';
|
import Validation from '../validation';
|
||||||
|
import type { Row } from './types';
|
||||||
type Row = {
|
|
||||||
value: number;
|
|
||||||
status: Status;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default class PaymentsTable {
|
export default class PaymentsTable {
|
||||||
root: RootStore;
|
root: RootStore;
|
||||||
|
|||||||
6
stores/tables/payments/types.ts
Normal file
6
stores/tables/payments/types.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import type { Status } from 'Elements/types';
|
||||||
|
|
||||||
|
export type Row = {
|
||||||
|
value: number;
|
||||||
|
status: Status;
|
||||||
|
};
|
||||||
@ -29,6 +29,7 @@ export default class Validation {
|
|||||||
|
|
||||||
const removeError = () => {
|
const removeError = () => {
|
||||||
this.messages.delete(message);
|
this.messages.delete(message);
|
||||||
|
notification.close(this.params.err_key);
|
||||||
};
|
};
|
||||||
|
|
||||||
notification.error({
|
notification.error({
|
||||||
|
|||||||
39
tools/__tests__/array.test.js
Normal file
39
tools/__tests__/array.test.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { shift } from 'tools/array';
|
||||||
|
|
||||||
|
describe('tools/array', () => {
|
||||||
|
describe('[function] shift', () => {
|
||||||
|
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
|
||||||
|
test('should shift array right 3 positions', () => {
|
||||||
|
const result = shift(arr, 3);
|
||||||
|
expect(result).toEqual([7, 8, 9, 1, 2, 3, 4, 5, 6]);
|
||||||
|
});
|
||||||
|
test('should shift array left 3 positions', () => {
|
||||||
|
const result = shift(arr, -3);
|
||||||
|
expect(result).toEqual([4, 5, 6, 7, 8, 9, 1, 2, 3]);
|
||||||
|
});
|
||||||
|
test('should shift array right 6 positions', () => {
|
||||||
|
const result = shift(arr, 15);
|
||||||
|
expect(result).toEqual([4, 5, 6, 7, 8, 9, 1, 2, 3]);
|
||||||
|
});
|
||||||
|
test('should shift array left 6 positions', () => {
|
||||||
|
const result = shift(arr, -15);
|
||||||
|
expect(result).toEqual([7, 8, 9, 1, 2, 3, 4, 5, 6]);
|
||||||
|
});
|
||||||
|
test('should keep array as is', () => {
|
||||||
|
const result = shift(arr, 0);
|
||||||
|
expect(result).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||||
|
});
|
||||||
|
test('should keep array as is', () => {
|
||||||
|
const result = shift(arr, 9);
|
||||||
|
expect(result).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||||
|
});
|
||||||
|
test('should return empty array', () => {
|
||||||
|
const result = shift([], 0);
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
});
|
||||||
|
test('should return empty array', () => {
|
||||||
|
const result = shift([], 0);
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
31
tools/array.ts
Normal file
31
tools/array.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
export function areEqual<T>(arr1: ReadonlyArray<T>, arr2: ReadonlyArray<T>) {
|
||||||
|
return arr1.length === arr2.length && arr1.every((value, index) => value === arr2[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function difference<T>(arr1: ReadonlyArray<T>, arr2: ReadonlyArray<T>) {
|
||||||
|
if (arr1.length !== arr2.length) return null;
|
||||||
|
|
||||||
|
const changes = [];
|
||||||
|
// eslint-disable-next-line unicorn/no-for-loop
|
||||||
|
for (let i = 0; i < arr1.length; i += 1) {
|
||||||
|
if (arr1[i] !== arr2[i]) {
|
||||||
|
changes.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return changes;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function shift<T>(arr: Array<T>, n: number) {
|
||||||
|
if (arr.length === 0) return arr;
|
||||||
|
|
||||||
|
const shiftNumber = n % arr.length;
|
||||||
|
|
||||||
|
if (shiftNumber === 0) return arr;
|
||||||
|
|
||||||
|
return [...arr.slice(-shiftNumber, arr.length), ...arr.slice(0, -shiftNumber)];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isSorted(arr: Array<number>) {
|
||||||
|
return arr.every((value, index, array) => !index || array[index - 1] <= value);
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user