chore: strengthen backend eslint profile
This commit is contained in:
@@ -1,58 +1,58 @@
|
||||
import { planIncomePeriod } from './income/income.api';
|
||||
import { dateOnlyBounds, moscowDate } from './investment-calendar';
|
||||
import {
|
||||
operationIntervals,
|
||||
type OperationInterval,
|
||||
operationIntervals,
|
||||
} from './operations/domain/operations-sync-plan';
|
||||
|
||||
export type InvestmentScreen = {
|
||||
screen:
|
||||
| 'accounts'
|
||||
| 'account'
|
||||
| 'operations'
|
||||
| 'income'
|
||||
| 'events'
|
||||
| 'stocks'
|
||||
| 'bonds'
|
||||
| 'stock'
|
||||
| 'bond';
|
||||
accountId?: string;
|
||||
candlePeriod?: 'all' | 'halfYear' | 'month' | 'year';
|
||||
candleRange?: { from: Date; to: Date };
|
||||
from?: string;
|
||||
to?: string;
|
||||
instrumentUid?: string;
|
||||
query?: string;
|
||||
candlePeriod?: 'month' | 'halfYear' | 'year' | 'all';
|
||||
candleRange?: { from: Date; to: Date };
|
||||
screen:
|
||||
| 'account' |
|
||||
'accounts' |
|
||||
'bond' |
|
||||
'bonds' |
|
||||
'events' |
|
||||
'income' |
|
||||
'operations' |
|
||||
'stock' |
|
||||
'stocks';
|
||||
to?: string;
|
||||
};
|
||||
const defaultEventDays = 30;
|
||||
const incomeHistoryYears = 5;
|
||||
const candleMonths = { month: 1, halfYear: 6, year: 12 };
|
||||
|
||||
type Account = { accountId: string; openedOn: string | null };
|
||||
type Account = { accountId: string; openedOn: null | string };
|
||||
type Instrument = {
|
||||
uid: string;
|
||||
type: 'STOCK' | 'BOND';
|
||||
historyFrom: Date | null;
|
||||
type: 'BOND' | 'STOCK';
|
||||
uid: string;
|
||||
};
|
||||
type Source = {
|
||||
kind:
|
||||
| 'membership'
|
||||
| 'portfolio'
|
||||
| 'operations'
|
||||
| 'metadata'
|
||||
| 'catalog'
|
||||
| 'quote'
|
||||
| 'company'
|
||||
| 'metrics'
|
||||
| 'calendar'
|
||||
| 'candles';
|
||||
accountId?: string;
|
||||
instrumentUid?: string;
|
||||
instrumentType?: 'STOCK' | 'BOND';
|
||||
from?: Date;
|
||||
to?: Date;
|
||||
instrumentType?: 'BOND' | 'STOCK';
|
||||
instrumentUid?: string;
|
||||
kind:
|
||||
| 'calendar' |
|
||||
'candles' |
|
||||
'catalog' |
|
||||
'company' |
|
||||
'membership' |
|
||||
'metadata' |
|
||||
'metrics' |
|
||||
'operations' |
|
||||
'portfolio' |
|
||||
'quote';
|
||||
priority: 'discovery' | 'history' | 'screen';
|
||||
query?: string;
|
||||
priority: 'discovery' | 'screen' | 'history';
|
||||
to?: Date;
|
||||
};
|
||||
|
||||
// Expands screen scope, never credentials or arbitrary caller-supplied jobs.
|
||||
@@ -74,19 +74,19 @@ export function planInvestmentScreen(
|
||||
screen.accountId === 'all' ||
|
||||
account.accountId === screen.accountId,
|
||||
);
|
||||
const unavailable: { resourceKey: string; reason: string }[] = [];
|
||||
const unavailable: { reason: string; resourceKey: string }[] = [];
|
||||
const operations = (from: Date, to: Date, instrumentUid?: string) => {
|
||||
const targets = accounts.flatMap((account) =>
|
||||
from < to
|
||||
? [
|
||||
from < to ?
|
||||
[
|
||||
{
|
||||
accountId: account.accountId,
|
||||
from,
|
||||
to,
|
||||
...(instrumentUid ? { instrumentUid } : {}),
|
||||
},
|
||||
]
|
||||
: [],
|
||||
] :
|
||||
[],
|
||||
);
|
||||
sources.push(
|
||||
...operationIntervals(targets).map((range) => ({
|
||||
@@ -105,16 +105,18 @@ export function planInvestmentScreen(
|
||||
})),
|
||||
);
|
||||
const knownOpening =
|
||||
accounts.length && accounts.every((account) => account.openedOn)
|
||||
? new Date(
|
||||
accounts.length && accounts.every((account) => account.openedOn) ?
|
||||
new Date(
|
||||
// eslint-disable-next-line sonarjs/no-alphabetical-sort -- Code-unit ordering is intentional for canonical keys and ISO dates; locale collation would change persisted bindings.
|
||||
`${accounts.map((account) => account.openedOn ?? '').sort()[0]}T00:00:00+03:00`,
|
||||
)
|
||||
: undefined;
|
||||
) :
|
||||
undefined;
|
||||
if (!context.membershipKnown) return { sources, unavailable, now };
|
||||
if (
|
||||
['accounts', 'account', 'events', 'stock', 'bond'].includes(screen.screen)
|
||||
)
|
||||
['account', 'accounts', 'bond', 'events', 'stock'].includes(screen.screen)
|
||||
) {
|
||||
portfolio();
|
||||
}
|
||||
if (
|
||||
screen.screen === 'operations' ||
|
||||
screen.screen === 'stock' ||
|
||||
@@ -137,18 +139,19 @@ export function planInvestmentScreen(
|
||||
);
|
||||
for (const range of [
|
||||
period.selected,
|
||||
...(screen.screen === 'income'
|
||||
? [period.previous, ...(period.yearAgo ? [period.yearAgo] : [])]
|
||||
: []),
|
||||
...(screen.screen === 'income' ?
|
||||
[period.previous, ...(period.yearAgo ? [period.yearAgo] : [])] :
|
||||
[]),
|
||||
]) {
|
||||
const bounds = dateOnlyBounds(range, now);
|
||||
operations(bounds.from, bounds.to);
|
||||
}
|
||||
if (screen.screen === 'income' && !period.yearAgo)
|
||||
if (screen.screen === 'income' && !period.yearAgo) {
|
||||
unavailable.push({
|
||||
resourceKey: 'income/yearAgo', // gitleaks:allow public resource identifier
|
||||
reason: 'calendar_or_account_boundary',
|
||||
});
|
||||
}
|
||||
if (screen.screen === 'account') {
|
||||
const range = ledgerRange(
|
||||
screen.from,
|
||||
@@ -160,13 +163,14 @@ export function planInvestmentScreen(
|
||||
operations(range.from, range.to);
|
||||
}
|
||||
}
|
||||
if (screen.screen === 'stocks' || screen.screen === 'bonds')
|
||||
if (screen.screen === 'stocks' || screen.screen === 'bonds') {
|
||||
sources.push({
|
||||
kind: 'catalog',
|
||||
instrumentType: screen.screen === 'stocks' ? 'STOCK' : 'BOND',
|
||||
query: screen.query?.trim().toUpperCase() ?? '',
|
||||
priority: 'screen',
|
||||
});
|
||||
}
|
||||
const instruments = context.instruments.filter(
|
||||
(instrument) =>
|
||||
!screen.instrumentUid || instrument.uid === screen.instrumentUid,
|
||||
@@ -175,20 +179,22 @@ export function planInvestmentScreen(
|
||||
screen.instrumentUid &&
|
||||
!instruments.length &&
|
||||
(screen.screen === 'stock' || screen.screen === 'bond')
|
||||
)
|
||||
) {
|
||||
instruments.push({
|
||||
uid: screen.instrumentUid,
|
||||
type: screen.screen === 'stock' ? 'STOCK' : 'BOND',
|
||||
historyFrom: null,
|
||||
});
|
||||
}
|
||||
for (const instrument of instruments) {
|
||||
const identity = {
|
||||
instrumentUid: instrument.uid,
|
||||
instrumentType: instrument.type,
|
||||
};
|
||||
sources.push({ ...identity, kind: 'metadata', priority: 'screen' });
|
||||
if (['stocks', 'bonds', 'stock', 'bond'].includes(screen.screen))
|
||||
if (['bond', 'bonds', 'stock', 'stocks'].includes(screen.screen)) {
|
||||
sources.push({ ...identity, kind: 'quote', priority: 'screen' });
|
||||
}
|
||||
if (screen.screen === 'stock' || screen.screen === 'bond') {
|
||||
sources.push({
|
||||
...identity,
|
||||
@@ -197,47 +203,51 @@ export function planInvestmentScreen(
|
||||
});
|
||||
const from = new Date(now);
|
||||
const period = screen.candlePeriod ?? 'month';
|
||||
if (period !== 'all')
|
||||
if (period !== 'all') {
|
||||
// eslint-disable-next-line security/detect-object-injection -- The period belongs to the validated candle-period union.
|
||||
from.setUTCMonth(from.getUTCMonth() - candleMonths[period]);
|
||||
}
|
||||
const range =
|
||||
screen.candleRange ??
|
||||
(period === 'all'
|
||||
? instrument.historyFrom
|
||||
? { from: instrument.historyFrom, to: now }
|
||||
: null
|
||||
: { from, to: now });
|
||||
if (range)
|
||||
(period === 'all' ?
|
||||
instrument.historyFrom ?
|
||||
{ from: instrument.historyFrom, to: now } :
|
||||
null :
|
||||
{ from, to: now });
|
||||
if (range) {
|
||||
sources.push({
|
||||
...identity,
|
||||
kind: 'candles',
|
||||
...range,
|
||||
priority: 'screen',
|
||||
});
|
||||
else
|
||||
} else {
|
||||
unavailable.push({
|
||||
resourceKey: `candles/${instrument.uid}/all`,
|
||||
reason: 'history_start_unavailable',
|
||||
});
|
||||
}
|
||||
}
|
||||
if (['account', 'events', 'stock', 'bond'].includes(screen.screen)) {
|
||||
if (['account', 'bond', 'events', 'stock'].includes(screen.screen)) {
|
||||
const day =
|
||||
screen.screen === 'stock' || screen.screen === 'bond'
|
||||
? moscowDate(now)
|
||||
: now.toISOString().slice(0, 10);
|
||||
screen.screen === 'stock' || screen.screen === 'bond' ?
|
||||
moscowDate(now) :
|
||||
now.toISOString().slice(0, 10);
|
||||
const from =
|
||||
screen.screen === 'events' && screen.from
|
||||
? new Date(`${screen.from}T00:00:00Z`)
|
||||
: new Date(`${day}T00:00:00Z`);
|
||||
screen.screen === 'events' && screen.from ?
|
||||
new Date(`${screen.from}T00:00:00Z`) :
|
||||
new Date(`${day}T00:00:00Z`);
|
||||
const to =
|
||||
screen.screen === 'events' && screen.to
|
||||
? new Date(`${screen.to}T00:00:00Z`)
|
||||
: new Date(from);
|
||||
screen.screen === 'events' && screen.to ?
|
||||
new Date(`${screen.to}T00:00:00Z`) :
|
||||
new Date(from);
|
||||
if (
|
||||
!(screen.screen === 'events' && screen.to) &&
|
||||
screen.screen !== 'stock' &&
|
||||
screen.screen !== 'bond'
|
||||
)
|
||||
) {
|
||||
to.setUTCDate(to.getUTCDate() + defaultEventDays);
|
||||
}
|
||||
if (screen.screen === 'stock' || screen.screen === 'bond') {
|
||||
from.setUTCFullYear(from.getUTCFullYear() - incomeHistoryYears);
|
||||
to.setUTCFullYear(to.getUTCFullYear() + 1);
|
||||
@@ -252,9 +262,9 @@ export function planInvestmentScreen(
|
||||
}
|
||||
if (
|
||||
!force &&
|
||||
['account', 'operations', 'income', 'events'].includes(screen.screen)
|
||||
['account', 'events', 'income', 'operations'].includes(screen.screen)
|
||||
) {
|
||||
if (instrument.historyFrom)
|
||||
if (instrument.historyFrom) {
|
||||
sources.push({
|
||||
...identity,
|
||||
kind: 'candles',
|
||||
@@ -262,14 +272,15 @@ export function planInvestmentScreen(
|
||||
to: now,
|
||||
priority: 'history',
|
||||
});
|
||||
else
|
||||
} else {
|
||||
unavailable.push({
|
||||
resourceKey: `candles/${instrument.uid}/all`,
|
||||
reason: 'history_start_unavailable',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!force && !['stocks', 'bonds'].includes(screen.screen))
|
||||
if (!force && !['bonds', 'stocks'].includes(screen.screen)) {
|
||||
sources.push(
|
||||
...accountHistory(accounts, now).map((range) => ({
|
||||
...range,
|
||||
@@ -277,6 +288,7 @@ export function planInvestmentScreen(
|
||||
priority: 'history' as const,
|
||||
})),
|
||||
);
|
||||
}
|
||||
const priorities = { discovery: 0, screen: 1, history: 2 };
|
||||
sources.sort(
|
||||
(left, right) => priorities[left.priority] - priorities[right.priority],
|
||||
@@ -290,9 +302,9 @@ export function accountHistory(
|
||||
): OperationInterval[] {
|
||||
return accounts.flatMap((account) => {
|
||||
const from = new Date(
|
||||
account.openedOn
|
||||
? `${account.openedOn}T00:00:00Z`
|
||||
: '1970-01-01T00:00:00Z',
|
||||
account.openedOn ?
|
||||
`${account.openedOn}T00:00:00Z` :
|
||||
'1970-01-01T00:00:00Z',
|
||||
);
|
||||
return from < now ? [{ accountId: account.accountId, from, to: now }] : [];
|
||||
});
|
||||
@@ -305,21 +317,22 @@ export function ledgerRange(
|
||||
accounts: Account[],
|
||||
now: Date,
|
||||
) {
|
||||
const opening = accounts.every((account) => account.openedOn)
|
||||
? accounts.map((account) => account.openedOn ?? '').sort()[0]
|
||||
: undefined;
|
||||
const from = fromValue
|
||||
? new Date(fromValue)
|
||||
: instrumentUid
|
||||
? new Date(opening ? `${opening}T00:00:00Z` : '1970-01-01T00:00:00Z')
|
||||
: new Date(Date.UTC(now.getUTCFullYear(), 0, 1));
|
||||
const end = toValue
|
||||
? new Date(
|
||||
/^\d{4}-\d{2}-\d{2}$/.test(toValue)
|
||||
? `${toValue}T23:59:59.999Z`
|
||||
: toValue,
|
||||
)
|
||||
: now;
|
||||
const opening = accounts.every((account) => account.openedOn) ?
|
||||
// eslint-disable-next-line sonarjs/no-alphabetical-sort -- Code-unit ordering is intentional for canonical keys and ISO dates; locale collation would change persisted bindings.
|
||||
accounts.map((account) => account.openedOn ?? '').sort()[0] :
|
||||
undefined;
|
||||
const from = fromValue ?
|
||||
new Date(fromValue) :
|
||||
instrumentUid ?
|
||||
new Date(opening ? `${opening}T00:00:00Z` : '1970-01-01T00:00:00Z') :
|
||||
new Date(Date.UTC(now.getUTCFullYear(), 0, 1));
|
||||
const end = toValue ?
|
||||
new Date(
|
||||
/^\d{4}-\d{2}-\d{2}$/.test(toValue) ?
|
||||
`${toValue}T23:59:59.999Z` :
|
||||
toValue,
|
||||
) :
|
||||
now;
|
||||
return {
|
||||
from,
|
||||
to: new Date(Math.min(end.valueOf() + (toValue ? 1 : 0), now.valueOf())),
|
||||
|
||||
Reference in New Issue
Block a user