From 3cb0a6a73c7a8802d29382e2d3ebde15c9d50a46 Mon Sep 17 00:00:00 2001 From: Chika Date: Thu, 13 Oct 2022 14:04:23 +0300 Subject: [PATCH] =?UTF-8?q?tables/payments:=20=D0=A1=D0=B5=D0=B7=D0=BE?= =?UTF-8?q?=D0=BD=D0=BD=D1=8B=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/default-options.ts | 20 +- config/default-values.ts | 2 +- .../payments/lib/__tests__/seasons.test.js | 20 ++ process/payments/lib/seasons.ts | 65 +++++++ process/payments/reactions.ts | 176 ++++++++++++++++-- process/payments/validation.ts | 5 + tools/__tests__/array.test.js | 39 ++++ tools/array.ts | 16 +- 8 files changed, 315 insertions(+), 28 deletions(-) create mode 100644 process/payments/lib/__tests__/seasons.test.js create mode 100644 process/payments/lib/seasons.ts create mode 100644 tools/__tests__/array.test.js diff --git a/config/default-options.ts b/config/default-options.ts index 17bca62..60ef2be 100644 --- a/config/default-options.ts +++ b/config/default-options.ts @@ -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: [ { diff --git a/config/default-values.ts b/config/default-values.ts index 2db3c13..c6a66bb 100644 --- a/config/default-values.ts +++ b/config/default-values.ts @@ -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, diff --git a/process/payments/lib/__tests__/seasons.test.js b/process/payments/lib/__tests__/seasons.test.js new file mode 100644 index 0000000..d538a58 --- /dev/null +++ b/process/payments/lib/__tests__/seasons.test.js @@ -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); + }); + }); +}); diff --git a/process/payments/lib/seasons.ts b/process/payments/lib/seasons.ts new file mode 100644 index 0000000..66794fa --- /dev/null +++ b/process/payments/lib/seasons.ts @@ -0,0 +1,65 @@ +/* eslint-disable implicit-arrow-linebreak */ +import type { CalculationValues } from 'stores/calculation/values/types'; + +type SeasonType = NonNullable; +type LeasingPeriod = CalculationValues['leasingPeriod']; + +const SEASONS_PERIODS: Record> = { + 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) { + return SEASONS_PERIODS[seasonType].map((index) => seasons[index]); +} + +export function generateSeasons(seasonType: SeasonType, values: Array) { + const positions = SEASONS_PERIODS[seasonType]; + + const seasons: Array = []; + + [...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) { + 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)) + ); +} diff --git a/process/payments/reactions.ts b/process/payments/reactions.ts index a1cf67a..98fe113 100644 --- a/process/payments/reactions.ts +++ b/process/payments/reactions.ts @@ -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, } ); diff --git a/process/payments/validation.ts b/process/payments/validation.ts index 1fd41f1..83f97d8 100644 --- a/process/payments/validation.ts +++ b/process/payments/validation.ts @@ -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; diff --git a/tools/__tests__/array.test.js b/tools/__tests__/array.test.js new file mode 100644 index 0000000..964b9ec --- /dev/null +++ b/tools/__tests__/array.test.js @@ -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([]); + }); + }); +}); diff --git a/tools/array.ts b/tools/array.ts index fd75999..f468627 100644 --- a/tools/array.ts +++ b/tools/array.ts @@ -4,12 +4,24 @@ export function areEqual(arr1: ReadonlyArray, arr2: ReadonlyArray) { export function difference(arr1: ReadonlyArray, arr2: ReadonlyArray) { 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(arr: Array, 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)]; }