Files
evo-sync/docs/plans/catalog-browser.md
mguschin 9aeef73b10 feat: release v1.8.0 — connections dashboard, VK OAuth, sync config, catalog browser
- Connections dashboard with add/remove flow and background health checks
- VK OAuth connection with profile info and health monitoring
- Sync configuration page with master toggle and filter summary
- Catalog browser with store/group/product tables, filter management, CSV export
- Alembic migrations for all new tables
- run/read_config.py for shell sync script DB integration
- CHANGELOG.md updated for v1.8.0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 16:08:19 +03:00

263 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Catalog Browser with Filter Management & CSV Export
## Context
Users need to browse their Evotor catalog (stores, groups, products) in a table view, manage sync whitelist/blacklist rules inline, and export data to CSV.
This feature **replaces** the separate `/sync/stores`, `/sync/groups`, `/sync/products` pages from the sync-configuration plan. The catalog browser becomes the unified place for both viewing data and managing filter rules.
Data is cached in DB with a refresh mechanism — not fetched live on every page load.
## Data Model
### Catalog Cache Tables
```
tablename: "cached_stores"
- id (Integer, PK)
- user_id (Integer, FK users.id CASCADE)
- evotor_id (String 255) # Evotor UUID
- name (String 255)
- address (String 500, nullable)
- fetched_at (DateTime) # when this snapshot was taken
UniqueConstraint: (user_id, evotor_id)
Index: user_id
```
```
tablename: "cached_groups"
- id (Integer, PK)
- user_id (Integer, FK users.id CASCADE)
- evotor_id (String 255) # Evotor UUID
- store_evotor_id (String 255) # parent store UUID
- name (String 255)
- fetched_at (DateTime)
UniqueConstraint: (user_id, evotor_id)
Index: (user_id, store_evotor_id)
```
```
tablename: "cached_products"
- id (Integer, PK)
- user_id (Integer, FK users.id CASCADE)
- evotor_id (String 255) # Evotor UUID
- store_evotor_id (String 255) # parent store UUID
- group_evotor_id (String 255, nullable) # parent group UUID
- name (String 255)
- price (Numeric(12,2), nullable)
- quantity (Numeric(12,3), nullable)
- measure_name (String 20, nullable)
- article_number (String 100, nullable)
- allow_to_sell (Boolean, nullable)
- fetched_at (DateTime)
UniqueConstraint: (user_id, evotor_id)
Index: (user_id, store_evotor_id, group_evotor_id)
```
### `SyncFilter` (from sync-configuration plan, unchanged)
```
tablename: "sync_filters"
- sync_config_id, entity_type, entity_id, entity_name, filter_mode, parent_entity_id
```
The catalog browser reads from cache tables for display and from `sync_filters` for the current filter state of each entity.
### Cache Refresh
`web/evotor_api.py` gets a new function:
```python
async def refresh_catalog_cache(user_id: int, access_token: str, db: Session):
"""Fetch all stores, groups, products from Evotor API and upsert into cache tables."""
```
Triggered by:
- Manual "Обновить" button on the catalog page
- Background job (optional, can reuse health_checker interval or separate setting)
- First visit to catalog if cache is empty
## Plan
### 1. New Models — `web/models.py`
Add `CachedStore`, `CachedGroup`, `CachedProduct` models as described above.
### 2. Alembic Migration
Create `cached_stores`, `cached_groups`, `cached_products` tables.
### 3. Evotor API Helper — `web/evotor_api.py`
Extend with:
```python
async def fetch_stores(access_token: str) -> list[dict]
async def fetch_groups(access_token: str, store_id: str) -> list[dict]
async def fetch_products(access_token: str, store_id: str) -> list[dict]
async def refresh_catalog_cache(user_id: int, access_token: str, db: Session)
```
`refresh_catalog_cache` does:
1. Fetch all stores
2. For each store, fetch groups and products
3. Upsert into cache tables (delete old rows for user, insert fresh)
4. Update `fetched_at` timestamps
### 4. Catalog Route — `web/routes/catalog.py` (new)
**`GET /catalog`** — Stores table. Requires auth + Evotor connection.
- Reads `cached_stores` for user
- If cache is empty, triggers refresh
- Shows table with columns: Название, Адрес, Статус фильтра, Действия
- Each row shows the store's current `SyncFilter` state (included/excluded/no rule)
- Link to drill into groups for each store
- "Обновить каталог" button, "Экспорт CSV" button, back link
**`GET /catalog/groups?store_id=UUID`** — Groups table for a store.
- Reads `cached_groups` filtered by `store_evotor_id`
- Table columns: Название, Статус фильтра, Кол-во товаров, Действия
- Each row shows group's `SyncFilter` state
- Link to drill into products for each group
- "Экспорт CSV" button, back to stores
**`GET /catalog/products?store_id=UUID&group_id=UUID`** — Products table for a group.
- Reads `cached_products` filtered by `store_evotor_id` and `group_evotor_id`
- Table columns: Название, Артикул, Цена, Кол-во, Ед. изм., В продаже, Статус фильтра, Действия
- Each row shows product's `SyncFilter` state
- "Экспорт CSV" button, back to groups
**`GET /catalog/products?store_id=UUID`** — All products for a store (no group filter).
- Same table, but shows all products in the store with a "Группа" column added
**`POST /catalog/filter`** — Toggle filter for an entity.
- Body: `entity_type`, `entity_id`, `entity_name`, `filter_mode` (include/exclude/none), `parent_entity_id`
- Creates, updates, or deletes the `SyncFilter` row
- Redirects back to the referring page
**`POST /catalog/refresh`** — Manual cache refresh.
- Calls `refresh_catalog_cache()`
- Redirects back to `/catalog`
**`GET /catalog/export?type=stores|groups|products&store_id=UUID&group_id=UUID`** — CSV export.
- Reads from cache tables
- Returns `StreamingResponse` with `text/csv` content type and `Content-Disposition: attachment`
- Filename: `{type}_{date}.csv`
### 5. Templates
**`web/templates/catalog_stores.html`** — Stores table:
```
┌──────────────────────────────────────────────────────────────┐
│ Каталог [Обновить] [Экспорт CSV] │
│ Последнее обновление: 06.03.2026 14:30 │
├──────────────────────────────────────────────────────────────┤
│ │
│ Магазины │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Название │ Адрес │ Фильтр │ │ │
│ ├──────────────────────────────────────────────────────┤ │
│ │ Чайная │ ул. Мира, 1 │ ✓ Вкл │ [→][▼] │ │
│ │ Склад │ — │ ✗ Выкл │ [→][▼] │ │
│ │ Точка 2 │ ул. Мира, 5 │ — Нет │ [→][▼] │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ [→] = перейти к группам │
│ [▼] = dropdown: Включить / Исключить / Убрать правило │
│ │
└──────────────────────────────────────────────────────────────┘
```
**Filter status column** shows:
- `✓ Включено` (green badge) — entity has an "include" rule
- `✗ Исключено` (red badge) — entity has an "exclude" rule
- `— Нет правила` (grey badge) — no filter rule (follows default behavior)
**Actions column** per row:
- Link icon → drill into children (groups for stores, products for groups)
- Dropdown button with filter actions: "Включить в синхронизацию" / "Исключить из синхронизации" / "Убрать правило". Each is a small POST form to `/catalog/filter`.
**`web/templates/catalog_groups.html`** — Groups table:
- Breadcrumb: Каталог > {Store name} > Группы
- Same table pattern, columns: Название, Кол-во товаров, Фильтр, Действия
- Drill-down link to products per group
**`web/templates/catalog_products.html`** — Products table:
- Breadcrumb: Каталог > {Store name} > {Group name} > Товары
- Columns: Название, Артикул, Цена, Кол-во, Ед. изм., В продаже, Фильтр, Действия
- "В продаже" column: green check / red cross based on `allow_to_sell`
All tables use Bootstrap table styling (`table table-striped table-hover`) with responsive wrapper.
### 6. CSV Export Format
**Stores CSV:**
```
Название,Адрес,ID,Фильтр
Чайная,"ул. Мира, 1",uuid-123,Включено
```
**Groups CSV:**
```
Магазин,Название,ID,Фильтр
Чайная,Белый чай,uuid-456,Включено
```
**Products CSV:**
```
Магазин,Группа,Название,Артикул,Цена,Количество,Ед. измерения,В продаже,ID,Фильтр
Чайная,Белый чай,Бай Му Дань,1005,350.00,180.0,г,Да,uuid-789,Включено
```
UTF-8 with BOM (`\ufeff`) for Excel compatibility. Delimiter: comma.
### 7. Update Sync Configuration Plan
The `/sync` page links to `/catalog` instead of separate filter pages:
- "Настроить фильтры" button → `/catalog`
- Filter summary on `/sync` reads from `SyncFilter` table (unchanged)
- Remove `/sync/stores`, `/sync/groups`, `/sync/products` routes from sync-configuration plan — replaced by catalog browser
### 8. Navbar / Navigation
Add "Каталог" link to navbar for logged-in users. Order: Подключения → Каталог → Синхронизация → Личный кабинет → Выход.
### 9. Register Route — `web/main.py`
```python
from web.routes import catalog
app.include_router(catalog.router)
```
## Files Summary
| File | Action |
|------|--------|
| `web/models.py` | Modify — add `CachedStore`, `CachedGroup`, `CachedProduct` |
| `web/evotor_api.py` | Create — API fetch + cache refresh functions |
| `web/routes/catalog.py` | Create — catalog routes (tables, filter toggle, refresh, CSV export) |
| `web/templates/catalog_stores.html` | Create — stores table |
| `web/templates/catalog_groups.html` | Create — groups table |
| `web/templates/catalog_products.html` | Create — products table |
| `web/templates/base.html` | Modify — add "Каталог" nav link |
| `web/main.py` | Modify — register catalog router |
| `docs/plans/sync-configuration.md` | Update — remove /sync/stores,groups,products; link to /catalog |
| Alembic migration | Create — cache tables |
## Verification
1. Run `alembic upgrade head`
2. Visit `/catalog` without Evotor connection → warning to connect first
3. Connect Evotor, visit `/catalog` → triggers first cache refresh, shows stores table
4. Click store → shows groups table with group names from Evotor
5. Click group → shows products table with full product details
6. Toggle filter on a product → badge changes, `SyncFilter` row created in DB
7. Go to `/sync` → filter summary reflects the change
8. Click "Экспорт CSV" on products page → downloads CSV, opens correctly in Excel
9. Click "Обновить каталог" → re-fetches from Evotor API, updates cache
10. Verify breadcrumb navigation works correctly through the hierarchy