chore: remove plans and uploads from git tracking

This commit is contained in:
Kirill
2026-05-15 20:32:52 +05:00
parent 7e231dbdd8
commit d499aeb735
29 changed files with 16 additions and 1474 deletions
+6
View File
@@ -9,3 +9,9 @@ server/prisma/dev.db-journal
# Image resize cache
uploads/.cache/
# Server uploads directory (images)
server/uploads/
# Plans and design docs
.opencode/plans/
@@ -1,174 +0,0 @@
# Design: Доработка товара — удаление «под заказ», обязательные quantity и категория
**Дата:** 2026-05-15
**Статус:** На согласовании
## Цель
Упростить модель товара: убрать концепцию «под заказ», сделать количество и категорию обязательными полями. Категория «Не указано» остаётся технической заглушкой для переноса товаров при удалении категории, но не видна в каталоге и не выбирается при редактировании.
## Архитектура изменений
### 1. База данных (Prisma)
**Миграция:**
- Перед удалением полей: все товары с `inStock = false` получают `quantity = 0`
- Удалить поля `inStock` и `leadTimeDays` из модели `Product`
- Статус наличия определяется исключительно по `quantity`:
- `quantity > 0` → «В наличии»
- `quantity = 0` → «Нет в наличии»
**`server/prisma/schema.prisma`:**
```prisma
model Product {
// ... остальные поля без изменений ...
quantity Int @default(0)
// УДАЛЕНО: inStock Boolean @default(true)
// УДАЛЕНО: leadTimeDays Int?
category Category @relation(fields: [categoryId], references: [id], onDelete: Restrict)
categoryId String
// ...
}
```
### 2. Сервер — валидация и CRUD
**`server/src/routes/api/admin-products.js`:**
**CREATE (POST):**
- `quantity` — required, `Int >= 0` (было nullable)
- `categoryId` — required (было: при пустом → авто-назначение «Не указано»)
- Удалить валидацию `leadTimeDays` при `!inStock`
- Удалить принудительную установку `quantity = 1` для «под заказ»
- Вернуть 400: `'Укажите категорию'` если `categoryId` отсутствует
**UPDATE (PATCH):**
- `quantity` — required, `Int >= 0` (было nullable)
- `categoryId` — required (было: при пустом → «Не указано»)
- Удалить логику очистки `leadTimeDays` при `inStock = true`
- Удалить принудительную установку `quantity = 1`
- Вернуть 400 при отсутствии `categoryId`
**JSON Schema:**
- `CREATE_PRODUCT_SCHEMA`: убрать `leadTimeDays`, сделать `quantity` required (убрать `nullable`)
- `PATCH_PRODUCT_SCHEMA`: убрать `leadTimeDays`, `quantity` — если передан, то `>= 0`
**`server/src/routes/api/public-catalog.js`:**
- Удалить ветку `availability === 'in_stock'` и `availability === 'made_to_order'`
- Фильтрация «в наличии» больше не нужна — все товары в каталоге
### 3. Клиент — админка (две страницы)
**`client/src/pages/admin/ui/AdminPage.tsx`** и **`client/src/pages/admin-products/ui/AdminProductsPage.tsx`:**
**FormState:**
- Удалить `inStock: boolean` и `leadTimeDays: string`
- `quantity: string` — без nullable-семантики
**UI:**
- Удалить Switch «В наличии / Под заказ»
- Удалить TextField «Срок исполнения, дней»
- TextField «Количество»:
- Без helper «Оставьте пустым...»
- Новый helper: «0 = нет в наличии»
- Валидация: не может быть пустым, `parseInt >= 0`
- Select «Категория»:
- Удалить `<MenuItem value="">` с «Не указано»
- Валидация: не даёт сохранить без выбранной категории
- Показать ошибку при попытке сохранить без категории
**Submit-валидация:**
- Удалить проверку `leadTimeDays` при `!inStock`
- Добавить проверку: `categoryId` не пустой → blocking error
- Добавить проверку: `quantity` не пустой → blocking error
### 4. Клиент — каталог
**`client/src/entities/product/ui/ProductCard.tsx`:**
- Удалить логику `'Под заказ · {leadTimeDays} дн.'`
- Новый статус:
- `quantity > 0` → «В наличии» (зелёный)
- `quantity === 0` → «Нет в наличии» (серый/red)
**`client/src/pages/product/ui/ProductPage.tsx`:**
- Удалить chip `'Под заказ · {leadTimeDays} дн.'`
- Удалить alert `'Этот товар изготавливается под заказ...'`
- Статус определяется по `quantity`
**`client/src/pages/checkout/ui/CheckoutPage.tsx`:**
- Удалить определение made-to-order товаров в корзине
- Удалить info alert о доставке после изготовления
### 5. Клиент — фильтры
**`client/src/pages/home/lib/use-product-filters.ts`:**
- Удалить `availability: 'all' | 'in_stock' | 'made_to_order'` из state
- Удалить `availability` из параметров `fetchPublicProducts()`
**`client/src/pages/home/ui/ProductFilters.tsx`:**
- Удалить `ToggleButtonGroup` с `'all'`, `'in_stock'`, `'made_to_order'`
- Удалить отображение категории «Не указано» из списка чипов (фильтр `cat.slug !== 'ne-ukazano'`)
### 6. Категория «Не указано» — что остаётся
| Где | Что происходит |
|---|---|
| `server/src/lib/default-category.js` | **Остаётся** — функция `getOrCreateUnspecifiedCategory()` |
| `server/src/index.js` | **Остаётся** — вызов при старте |
| `server/src/routes/api/admin-categories.js` | **Остаётся** — нельзя удалить/переименовать; при удалении категории товары переезжают в «Не указано» |
| Админка категорий | **Остаётся** — кнопка удаления заблокирована |
| Фильтры каталога | **Скрыта** — не показывается в чипах |
| Форма товара | **Скрыта** — не выбирается в Select |
## Статус товара — новая логика
```
quantity > 0 → «В наличии» (зелёный chip/badge)
quantity = 0 → «Нет в наличии» (серый chip/badge)
```
Никаких других статусов. Поле `inStock` больше не существует.
## Файлы для изменения
### Сервер
| Файл | Изменения |
|---|---|
| `server/prisma/schema.prisma` | Удалить `inStock`, `leadTimeDays` |
| `server/src/routes/api/admin-products.js` | Валидация, schema, убрать логику под заказ |
| `server/src/routes/api/public-catalog.js` | Убрать фильтр availability |
### Клиент
| Файл | Изменения |
|---|---|
| `client/src/pages/admin/ui/AdminPage.tsx` | FormState, UI, валидация |
| `client/src/pages/admin-products/ui/AdminProductsPage.tsx` | FormState, UI, валидация |
| `client/src/entities/product/ui/ProductCard.tsx` | Статус по quantity |
| `client/src/pages/product/ui/ProductPage.tsx` | Убрать под заказ UI |
| `client/src/pages/checkout/ui/CheckoutPage.tsx` | Убрать made-to-order detection |
| `client/src/pages/home/ui/ProductFilters.tsx` | Убрать availability toggle, скрыть «Не указано» |
| `client/src/pages/home/lib/use-product-filters.ts` | Убрать `availability` |
## Миграция данных
```javascript
// В Prisma migration:
// 1. UPDATE Product SET quantity = 0 WHERE inStock = false
// 2. ALTER TABLE Product DROP COLUMN inStock
// 3. ALTER TABLE Product DROP COLUMN leadTimeDays
```
## Тестирование
**Сервер:**
- CREATE без categoryId → 400
- CREATE без quantity → 400
- CREATE с quantity = 0 → OK
- PATCH без categoryId → 400
- PATCH с quantity = 0 → OK
**Клиент:**
- Форма не сохраняется без категории
- Форма не сохраняется без количества
- Фильтры не содержат «Под заказ» и «Не указано»
- Карточка товара показывает «Нет в наличии» при quantity = 0
File diff suppressed because it is too large Load Diff
+10 -5
View File
@@ -14,19 +14,18 @@ describe('persistMultipartImages with eager mode', () => {
await fs.promises.unlink(path.join(UPLOADS_DIR, file)).catch(() => {})
}
}
const cacheDir = path.join(UPLOADS_DIR, '.cache')
await fs.promises.rm(cacheDir, { recursive: true, force: true }).catch(() => {})
})
it('returns WebP URLs when eager=true', async () => {
const sharp = (await import('sharp')).default
const testImagePath = path.join(UPLOADS_DIR, `${TEST_PREFIX}original.png`)
const filesBefore = await fs.promises.readdir(UPLOADS_DIR)
await sharp({ create: { width: 100, height: 100, channels: 3, background: { r: 255, g: 0, b: 0 } } })
.png()
.toFile(testImagePath)
const filesBefore = await fs.promises.readdir(UPLOADS_DIR)
const mockRequest = {
isMultipart: () => true,
parts: async function* () {
@@ -51,7 +50,13 @@ describe('persistMultipartImages with eager mode', () => {
// Verify the intermediate PNG file written by persistMultipartImages was deleted
const filesAfter = await fs.promises.readdir(UPLOADS_DIR)
const newPngFiles = filesAfter.filter((f) => !filesBefore.includes(f) && f.endsWith('.png'))
const newPngFiles = filesAfter.filter(
(f) =>
!filesBefore.includes(f) &&
f.endsWith('.png') &&
f !== path.basename(testImagePath) &&
!f.startsWith('test-eager-uuid-'),
)
expect(newPngFiles).toHaveLength(0)
})
Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

-1
View File
@@ -1 +0,0 @@
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB