tables/payments: Сезонные

This commit is contained in:
Chika 2022-10-13 14:04:23 +03:00
parent 2d2d5d6cc3
commit 3cb0a6a73c
8 changed files with 315 additions and 28 deletions

View File

@ -36,6 +36,16 @@ export const selectSeasonType = [
},
];
const selectHighSeasonStart = Array.from(
{
length: 12,
},
(_, i) => ({
label: `${i + 2}`,
value: 100_000_000 + i,
})
);
const defaultOptions: CalculationOptions = {
radioLastPaymentRule: [
{
@ -88,15 +98,7 @@ const defaultOptions: CalculationOptions = {
selectSeasonType,
selectHighSeasonStart: Array.from(
{
length: 12,
},
(_, i) => ({
label: `${i + 2}`,
value: 100_000_000 + i,
})
),
selectHighSeasonStart,
radioDeliveryTime: [
{

View File

@ -24,7 +24,7 @@ const defaultValues: CalculationValues = {
graphType: 100_000_000,
parmentsDecreasePercent: 94,
seasonType: null,
highSeasonStart: 100_000_000,
highSeasonStart: null,
comissionPerc: 0,
comissionRub: 0,
saleBonus: 1.3,

View File

@ -0,0 +1,20 @@
import { getSeasonsIndex } from '../seasons';
describe('process/payments/lib/seasons', () => {
describe('[function] getSeasonsIndex', () => {
test('should return 0', () => {
const result = getSeasonsIndex(0);
expect(result).toEqual(0);
});
test('should return 7', () => {
const result = getSeasonsIndex(7);
expect(result).toEqual(7);
});
test('should return 7', () => {
const result = getSeasonsIndex(55);
expect(result).toEqual(7);
});
});
});

View File

@ -0,0 +1,65 @@
/* eslint-disable implicit-arrow-linebreak */
import type { CalculationValues } from 'stores/calculation/values/types';
type SeasonType = NonNullable<CalculationValues['seasonType']>;
type LeasingPeriod = CalculationValues['leasingPeriod'];
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 SEASONS_PERIOD_NUMBER = 12;
export function getSeasonsIndex(paymentsIndex: number) {
return paymentsIndex - Math.floor(paymentsIndex / SEASONS_PERIOD_NUMBER) * SEASONS_PERIOD_NUMBER;
}
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))
);
}

View File

@ -7,7 +7,8 @@ import { comparer, reaction, toJS } from 'mobx';
import type { CalculationOptions } from 'stores/calculation/options/types';
import type RootStore from 'stores/root';
import type { Row } from 'stores/tables/payments/types';
import { difference } from 'tools/array';
import { difference, shift } from 'tools/array';
import * as seasonsTools from './lib/seasons';
import validatePaymentsTable from './validation';
export default function paymentsReactions(
@ -189,6 +190,7 @@ export default function paymentsReactions(
/**
* Дегрессия
*/
// 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,
]);
@ -329,39 +331,181 @@ export default function paymentsReactions(
},
(nextParams, prevParams) => {
if (nextParams.graphType === 100_000_001 && nextParams.degressionType === 100_000_007) {
const index = difference(nextParams.payments, prevParams.payments);
const changes = difference(nextParams.payments, prevParams.payments);
if (index) {
const value = nextParams.payments[index];
const payments = nextParams.payments.slice(1, -1).map((payment, i) => {
if (i <= index - 2) return payment;
if (changes === null || changes.length > 1) return;
return value;
});
const [changeIndex] = changes;
const value = nextParams.payments[changeIndex];
const payments = nextParams.payments.slice(1, -1).map((payment, i) => {
if (i <= changeIndex - 2) return payment;
const firstPaymentPerc = $calculation.getElementValue('tbxFirstPaymentPerc');
const lastPaymentPerc = $calculation.getElementValue('tbxLastPaymentPerc');
return value;
});
$tables.payments.setValues([firstPaymentPerc, ...payments, lastPaymentPerc]);
}
const firstPaymentPerc = $calculation.getElementValue('tbxFirstPaymentPerc');
const lastPaymentPerc = $calculation.getElementValue('tbxLastPaymentPerc');
$tables.payments.setValues([firstPaymentPerc, ...payments, lastPaymentPerc]);
}
},
{
delay: 10,
delay: 50,
equals: comparer.structural,
}
);
let removeError: () => void;
/**
* Сезонный
*/
reaction(
() => {
const graphType = $calculation.getElementValue('radioGraphType');
const payments = toJS($tables.payments.values);
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([]);
}
}
);
const defaultSeasonsValues = [100, 75, 50];
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, defaultSeasonsValues);
const payments = seasonsTools.generateSeasonsPayments(leasingPeriod, seasons);
const rows: Row[] = payments.map((value) => ({
value,
status: 'Default',
}));
const firstPaymentPerc = $calculation.getElementValue('tbxFirstPaymentPerc');
const lastPaymentPerc = $calculation.getElementValue('tbxLastPaymentPerc');
const shiftNumber = Number.parseInt(highSeasonStartOption.label, 10) - 2;
$tables.payments.setRows([
{
value: firstPaymentPerc,
status: 'Disabled',
},
...shift(rows, shiftNumber),
{
value: lastPaymentPerc,
status: 'Disabled',
},
]);
}
);
reaction(
() => {
const payments = toJS($tables.payments.values);
return payments;
},
(nextPayments, prevPayments) => {
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 unshiftedNextPayments = shift(nextPayments.slice(1, -1), -shiftNumber);
const unshiftedPrevPayments = shift(prevPayments.slice(1, -1), -shiftNumber);
const changes = difference(unshiftedNextPayments, unshiftedPrevPayments);
if (changes === null || changes.length > 1) return;
const [changeIndex] = changes;
const nextSeasons = unshiftedNextPayments.slice(0, seasonsTools.SEASONS_PERIOD_NUMBER + 1);
const values = seasonsTools.getSeasonsValues(seasonType, nextSeasons);
const positionIndex = seasonsTools.getPositionIndex(
seasonType,
seasonsTools.getSeasonsIndex(changeIndex)
);
values[positionIndex] = unshiftedNextPayments[changeIndex];
const seasons = seasonsTools.generateSeasons(seasonType, values);
const leasingPeriod = $calculation.getElementValue('tbxLeasingPeriod');
const payments = seasonsTools.generateSeasonsPayments(leasingPeriod, seasons);
const rows: Row[] = payments.map((value) => ({
value,
status: 'Default',
}));
const firstPaymentPerc = $calculation.getElementValue('tbxFirstPaymentPerc');
const lastPaymentPerc = $calculation.getElementValue('tbxLastPaymentPerc');
$tables.payments.setRows([
{
value: firstPaymentPerc,
status: 'Disabled',
},
...shift(rows, shiftNumber),
{
value: lastPaymentPerc,
status: 'Disabled',
},
]);
},
{
delay: 50,
equals: comparer.structural,
}
);
/**
* Валидация
*/
let removeError: () => void;
reaction(
() => {
const payments = toJS($tables.payments.values);
const graphType = $calculation.getElementValue('radioGraphType');
const seasonType = $calculation.getElementValue('selectSeasonType');
const highSeasonStart = $calculation.getElementValue('selectHighSeasonStart');
return {
payments,
graphType,
seasonType,
highSeasonStart,
};
},
({ payments }) => {
@ -373,7 +517,7 @@ export default function paymentsReactions(
}
},
{
delay: 10,
delay: 50,
equals: comparer.structural,
}
);

View File

@ -79,6 +79,10 @@ export default function validatePaymentsTable(
return 'Не выбран тип сезонности';
}
if (!values.highSeasonStart) {
return 'Не выбрано смещение сезонности';
}
break;
}
@ -95,6 +99,7 @@ export default function validatePaymentsTable(
break;
}
default:
return null;
}
return null;

View 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([]);
});
});
});

View File

@ -4,12 +4,24 @@ export function areEqual<T>(arr1: ReadonlyArray<T>, arr2: ReadonlyArray<T>) {
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]) {
return i;
changes.push(i);
}
}
return null;
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)];
}