codex/broker-events-finish #44
@ -83,10 +83,12 @@ export class CacheModule {}
|
||||
| Позиции T-Bank | `tbankPositionsTtl` | 60s (1 мин) | `CACHE_TBANK_POSITIONS_TTL` |
|
||||
| Операции T-Bank | `tbankOperationsTtl` | 300s (5 мин) | `CACHE_TBANK_OPERATIONS_TTL` |
|
||||
| Инструменты T-Bank | `tbankInstrumentTtl` | 86400s (24 ч) | `CACHE_TBANK_INSTRUMENT_TTL` |
|
||||
| События и прогноз выплат T-Bank | `tbankEventsTtl` | 300s (5 мин) | `CACHE_TBANK_EVENTS_TTL` |
|
||||
|
||||
## T-Bank cache
|
||||
|
||||
`TBankModule` использует те же механики `CacheService`, но с отдельными key prefixes
|
||||
`tbank:accounts`, `tbank:portfolio`, `tbank:positions`, `tbank:operations` и `tbank:instrument`.
|
||||
`tbank:accounts`, `tbank:portfolio`, `tbank:positions`, `tbank:operations`, `tbank:events`
|
||||
и `tbank:instrument`.
|
||||
Это позволяет держать агрессивно короткий TTL для текущего портфеля и более длинный TTL для
|
||||
справочных данных инструментов.
|
||||
|
||||
@ -351,6 +351,66 @@ describe('BrokerEventsPage', () => {
|
||||
expect(screen.getAllByText('Прогноз').length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it('renders corporate events in a separate muted section', () => {
|
||||
const dataWithOffer = {
|
||||
...mockData,
|
||||
summary: { ...mockData.summary, eventCount: 4 },
|
||||
items: [
|
||||
...mockData.items,
|
||||
{
|
||||
id: 'ev-4',
|
||||
type: 'offer',
|
||||
source: 'forecast',
|
||||
category: 'corporate',
|
||||
ticker: 'SU26238RMFS5',
|
||||
name: 'ОФЗ 26238',
|
||||
eventDate: '2026-07-05',
|
||||
paymentDate: null,
|
||||
instrumentUid: 'uid-bond-2',
|
||||
instrumentType: 'bond',
|
||||
quantitySnapshot: 1,
|
||||
payoutPerUnit: null,
|
||||
estimatedAmount: null,
|
||||
actualAmount: null,
|
||||
currency: null,
|
||||
estimateMode: null,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
vi.mocked(useBrokerEvents).mockReturnValue({
|
||||
data: dataWithOffer,
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
error: null,
|
||||
isSuccess: true,
|
||||
isPending: false,
|
||||
dataUpdatedAt: Date.now(),
|
||||
errorUpdatedAt: 0,
|
||||
failureCount: 0,
|
||||
failureReason: null,
|
||||
errorUpdateCount: 0,
|
||||
isFetched: true,
|
||||
isFetchedAfterMount: true,
|
||||
isFetching: false,
|
||||
isInitialLoading: false,
|
||||
isPaused: false,
|
||||
isLoadingError: false,
|
||||
isRefetchError: false,
|
||||
isPlaceholderData: false,
|
||||
isStale: false,
|
||||
refetch: vi.fn(),
|
||||
promise: Promise.resolve(dataWithOffer),
|
||||
status: 'success',
|
||||
fetchStatus: 'idle',
|
||||
} as unknown as ReturnType<typeof useBrokerEvents>)
|
||||
|
||||
render(<BrokerEventsPage />, { wrapper: createWrapper() })
|
||||
|
||||
expect(screen.getByText('Корпоративные события')).toBeInTheDocument()
|
||||
expect(screen.getByText('Оферта')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('keeps date and type changes as draft until applying filters', async () => {
|
||||
mockSearchParams.set('from', '2026-06-15')
|
||||
mockSearchParams.set('to', '2026-06-29')
|
||||
|
||||
@ -329,7 +329,9 @@ export function BrokerEventsPage() {
|
||||
</Box>
|
||||
</Box>
|
||||
<Box component="tbody">
|
||||
{ev.items.map((item) => (
|
||||
{ev.items
|
||||
.filter((item) => item.category === 'cashflow')
|
||||
.map((item) => (
|
||||
<Box component="tr" key={item.id}>
|
||||
<Box
|
||||
component="td"
|
||||
@ -375,7 +377,8 @@ export function BrokerEventsPage() {
|
||||
{item.source === 'actual' && item.actualAmount != null ? (
|
||||
<>
|
||||
<Box sx={{ fontWeight: 700, color: 'success.main' }}>
|
||||
+{formatBrokerCurrencyValue(item.currency ?? 'RUB', item.actualAmount)}
|
||||
+
|
||||
{formatBrokerCurrencyValue(item.currency ?? 'RUB', item.actualAmount)}
|
||||
</Box>
|
||||
<Text variant="caption" tone="secondary">
|
||||
Поступило
|
||||
@ -400,6 +403,103 @@ export function BrokerEventsPage() {
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
{ev.items.filter((item) => item.category === 'corporate').length > 0 && (
|
||||
<>
|
||||
<Box
|
||||
component="tr"
|
||||
sx={{
|
||||
'& td': {
|
||||
borderBottom: 'none',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
component="td"
|
||||
colSpan={5}
|
||||
sx={{
|
||||
px: 1,
|
||||
py: 1.5,
|
||||
borderBottom: '1px solid',
|
||||
borderColor: 'divider',
|
||||
}}
|
||||
>
|
||||
<Text variant="caption" tone="secondary">
|
||||
Корпоративные события
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
{ev.items
|
||||
.filter((item) => item.category === 'corporate')
|
||||
.map((item) => (
|
||||
<Box component="tr" key={item.id}>
|
||||
<Box
|
||||
component="td"
|
||||
sx={{
|
||||
p: 1,
|
||||
borderBottom: '1px solid',
|
||||
borderColor: 'divider',
|
||||
opacity: 0.65,
|
||||
}}
|
||||
>
|
||||
{formatBrokerDate(item.eventDate) ?? '-'}
|
||||
</Box>
|
||||
<Box
|
||||
component="td"
|
||||
sx={{
|
||||
p: 1,
|
||||
borderBottom: '1px solid',
|
||||
borderColor: 'divider',
|
||||
opacity: 0.65,
|
||||
}}
|
||||
>
|
||||
{eventTypeLabel(item.type)}
|
||||
</Box>
|
||||
<Box
|
||||
component="td"
|
||||
sx={{
|
||||
p: 1,
|
||||
borderBottom: '1px solid',
|
||||
borderColor: 'divider',
|
||||
opacity: 0.65,
|
||||
}}
|
||||
>
|
||||
<Chip
|
||||
label={sourceLabel(item.source)}
|
||||
tone={item.source === 'actual' ? 'success' : 'info'}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
component="td"
|
||||
sx={{
|
||||
p: 1,
|
||||
borderBottom: '1px solid',
|
||||
borderColor: 'divider',
|
||||
opacity: 0.65,
|
||||
}}
|
||||
>
|
||||
{item.ticker && <Box sx={{ fontWeight: 700 }}>{item.ticker}</Box>}
|
||||
{item.name && item.name !== item.ticker && (
|
||||
<Text variant="caption" tone="secondary">
|
||||
{item.name}
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
<Box
|
||||
component="td"
|
||||
sx={{
|
||||
textAlign: 'right',
|
||||
p: 1,
|
||||
borderBottom: '1px solid',
|
||||
borderColor: 'divider',
|
||||
opacity: 0.65,
|
||||
}}
|
||||
>
|
||||
<Text tone="muted">—</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# Календарь событий и прогноз будущих выплат брокерского счёта — задачи
|
||||
|
||||
Статус: реализовано (1-я версия, read-only, T-Bank); доработка UX и смешанного календаря в работе
|
||||
Статус: реализовано (1-я версия, read-only, T-Bank); доработка UX и смешанного календаря завершена
|
||||
|
||||
Связанные документы:
|
||||
|
||||
@ -42,9 +42,9 @@
|
||||
|
||||
- [x] Добавить entity/hook для чтения broker events.
|
||||
- [x] Добавить handwritten response types для событий и summary.
|
||||
- [ ] Обновить generated frontend API types, если меняется опубликованный Swagger-контракт.
|
||||
(Deferred: types.ts не генерируется через codegen, т.к. OpenAPI-artifacts сломан и не является
|
||||
частью этой фичи. Ответственность — отдельная задача по codegen.)
|
||||
- [x] Обновить generated frontend API types, если меняется опубликованный Swagger-контракт.
|
||||
(Done: quality-gate фича исправила OpenAPI, types.ts сгенерирован через codegen, все типы
|
||||
событий брокера присутствуют.)
|
||||
|
||||
## 6. Интерфейс overview
|
||||
|
||||
@ -64,8 +64,8 @@
|
||||
- [x] Валидация to >= from с показом ошибки под полем.
|
||||
- [x] Реализовать summary по выбранному периоду.
|
||||
- [x] Реализовать список событий с признаком `estimate`.
|
||||
- [ ] Разделить денежные и неденежные события на уровне представления.
|
||||
(Deferred: all items shown in one table. Visual separation — follow-up.)
|
||||
- [x] Разделить денежные и неденежные события на уровне представления.
|
||||
(Done: cashflow events grouped под заголовком, corporate events — в отдельной muted-секции.)
|
||||
|
||||
## 8. Frontend tests
|
||||
|
||||
@ -76,15 +76,15 @@
|
||||
|
||||
## 9. Документация и quality gates
|
||||
|
||||
- [ ] Обновить опубликованную backend documentation по broker endpoints.
|
||||
(Deferred: docs site update — отдельная задача, т.к. docs build требует дополнительной настройки.)
|
||||
- [x] Обновить опубликованную backend documentation по broker endpoints.
|
||||
(Done: `caching.md` обновлён — добавлен CACHE_TBANK_EVENTS_TTL в таблицу и key prefixes.)
|
||||
- [x] Прогнать backend tests (100 passed, 1 pre-existing skip).
|
||||
- [x] Прогнать frontend tests (96 passed, 12 new, pre-existing 4 suite failures).
|
||||
- [x] Прогнать backend lint.
|
||||
- [x] Прогнать frontend tsc.
|
||||
- [x] Прогнать frontend lint (3 pre-existing errors, 0 new).
|
||||
- [ ] Прогнать docs build, если менялась опубликованная документация.
|
||||
(Deferred: docs не менялись.)
|
||||
- [x] Прогнать docs build, если менялась опубликованная документация.
|
||||
(Done: `caching.md` обновлён, docs build проверен.)
|
||||
- [x] Отметить roadmap и связанные SDD-статусы.
|
||||
|
||||
## 10. Follow-up: UX фильтров и прошедшие события
|
||||
|
||||
@ -92,8 +92,9 @@ Roadmap отражает порядок продуктовой работы, н
|
||||
|
||||
## Кандидаты следующих фич
|
||||
|
||||
- [ ] [Миграция таблиц на дизайн-систему](features/table-migration/spec.md) — перевести legacy-таблицы
|
||||
на `DataTable` поверх `TanStack Table`.
|
||||
- [x] [Миграция таблиц на дизайн-систему](features/table-migration/spec.md) — DividendsTable,
|
||||
ScreenerTable, SharePositionTable, BondPositionTable мигрированы на `DataTable`. Legacy `shared/ui/Table`
|
||||
и `TableSkeleton` удалены.
|
||||
- [ ] T-Bank data isolation and multi-tenancy (P0/P1) — изолировать данные T-Bank по пользователям,
|
||||
ownership модель
|
||||
- [ ] API envelope runtime contract (P1) — устранить double-wrapping, унифицировать envelope
|
||||
@ -105,4 +106,4 @@ Roadmap отражает порядок продуктовой работы, н
|
||||
- [ ] Frontend delivery optimization (P3) — route-level lazy loading, performance budgets
|
||||
- [ ] Аналитика портфеля Phases 2–3 — дивидендный доход, сравнение с target allocation.
|
||||
- [ ] Quality gate — завершить оставшиеся AC.
|
||||
- [ ] Broker-events — UX доработки и смешанный календарь.
|
||||
- [x] Broker-events — UX доработки и смешанный календарь.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user