- ANALYSIS.md: append the 2026-10-04 findings (94 SKUs, hidden categories, broken link, PDP gaps, shipping contradiction, guest checkout) - CATALOG.md: full de-duplicated inventory (11 categories, 94 products with prices/badges), 3 extra PDP samples, checkout/payment field map, store constants - PLAN.md: locked decisions, route map, data model + ERD, key flows, seed plan, layer acceptance checks and open questions
20 KiB
La Rose — план переделки сайта (docs/PLAN.md)
Проект: SemianiakaVY/la-rose · Шаблон: online-shop · Стек: Next.js
Источники: SPEC.md (контракт), docs/ANALYSIS.md (факты об
источнике), docs/CATALOG.md (полный инвентарь каталога).
Дата: 2026-10-04.
0. Этап и рамки
Текущий этап — исследование, структура, схемы и карточки задач. На этом этапе:
- Нового дизайна НЕ делаем (решение зафиксировано в
.w4c/project.json) — отдельная дизайн-спецификация не выпускается; UI следует структуре источника и токенам, выбранным на слое фронтенда. - Реальные платежи не подключаем — провайдер за интерфейсом, реализация
mock. - Код приложения на этом этапе не пишется: результатом этапа являются этот план, mermaid-схемы и карточки задач на доске.
1. Цель продукта
Интернет-магазин доставки цветов и подарков по Минску (переделка la-rose.by): каталог с
категориями и фасетными фильтрами, карточка товара со структурированными полями, корзина и
одностраничное оформление, личный кабинет с историей заказов и закрытая админ-панель.
Продукт должен работать end-to-end на сид-данных (SPEC.md §1), UI — русский.
2. Зафиксированные решения
| # | Решение | Значение |
|---|---|---|
| R1 | Стек | Next.js (App Router, TypeScript) — из диалога создания проекта |
| R2 | Деньги | Только целые минорные единицы (priceMinor, копейки); никаких float |
| R3 | Форматирование чисел | Intl.NumberFormat / toLocaleString('en-US') с кодом валюты (SPEC.md §4) |
| R4 | Валюта | Целевая валюта магазина — BYN; в настройках шаблона доступны только USD/EUR/RUB, поэтому работаем через одну константу STORE_CURRENCY (интерим RUB). Переключение на BYN — правка одной константы; курсовой пересчёт в рамки не входит, цены сидов берём из аудита как есть |
| R5 | Платежи | Провайдер за одним интерфейсом PaymentProvider; при None (mock) — мок-адаптер, flow работает без ключей (SPEC.md §6) |
| R6 | Оформление | Оформление — одностраничное /checkout, как у источника (/order/): корзина → покупатель → доставка → оплата. Аккаунт: по SPEC.md §2 чекаут требует аккаунта, у источника гостевой чекаут возможен — берём SPEC как контракт (чекаут после входа), гостю показываем логин с сохранением корзины |
| R7 | Доставка | Один источник констант (lib/delivery/constants.ts): порог бесплатной доставки, тариф, за МКАД, срок, часы. Противоречие источника (50/80/100 BYN, см. ANALYSIS.md §4) снимается на одном значении |
| R8 | Фильтры | Фасеты: повод, цвет, тип цветка, размер/диапазон цены + сортировки как у источника (название, цена, хиты, оценка, дата, наличие) |
| R9 | PDP-контракт | Обязательные структурированные поля: состав, количество стеблей, высота/габариты, вариант/размер, уход, наличие, срок доставки (сейчас отсутствуют — ANALYSIS.md §3, CATALOG.md §4) |
| R10 | URL | Существующие человекочитаемые слаги категорий и товаров сохраняем 1:1; для товаров с числовыми /product/<id>/ готовим 301-карту на новые слаги (CATALOG.md §3) |
| R11 | Контент | Информационные страницы источника переносим: О компании, B2B, Доставка, Оплата, Как заказать, Возврат, Контакты, Вакансии, Политика; добавляем FAQ (нет у источника) |
| R12 | Аналитика и чат | Паритет по Яндекс.Метрике/GA4/FB Pixel закладываем; судьба чата Bitrix24 — открытый вопрос (см. §9) |
3. Информационная архитектура (маршруты)
Витрина
| Маршрут | Экран | Источник |
|---|---|---|
/ |
Главная: hero, категории, хиты | SPEC.md §3 |
/catalog |
Все товары, фильтры, сортировка, пагинация | SPEC.md §3, R8 |
/catalog/[categorySlug] |
Товары категории (11 категорий источника + скрытые, CATALOG.md §1–2) | SPEC.md §3 |
/product/[slug] |
Карточка: галерея, цена/старая цена, бейдж, варианты, PDP-контракт R9, в корзину | SPEC.md §3, R9 |
/cart |
Строки, количество, промокод, итоги | SPEC.md §3, ANALYSIS.md §4 |
/checkout |
Оформление (одна страница): покупатель → доставка → оплата | SPEC.md §5.2, R6 |
/checkout/success/[orderId] |
Подтверждение с номером заказа | SPEC.md §3 |
/account/orders, /account/orders/[orderId] |
История и детали заказа | SPEC.md §3 |
/login, /register, /logout |
Аутентификация | SPEC.md §3 |
/search?query= |
Поиск по названию/описанию | SPEC.md §6 |
/pages/[slug] |
Информационные страницы (R11) | ANALYSIS.md §5 |
Админ (за ролью admin)
| Маршрут | Экран |
|---|---|
/admin |
Дашборд: последние заказы, низкий остаток, выручка за период |
/admin/products |
Список/создание/правка/архив, изображения, цена и остаток |
/admin/categories |
Список/создание/правка/порядок |
/admin/orders, /admin/orders/[orderId] |
Список со статусами, детали и смена статуса |
Карта страниц
graph TD
HOME["/ — Главная"] --> CAT["/catalog — все товары (фасеты, сортировки)"]
CAT --> C1["/catalog/bukety-tsvetov (92)"]
CAT --> C2["/catalog/sbornye-bukety (36)"]
CAT --> C3["/catalog/bukety-iz-alstromerii (15)"]
CAT --> C4["/catalog/bukety-iz-gvozdiki (16)"]
CAT --> C5["/catalog/bukety-iz-kustovoy-rozy (20)"]
CAT --> C6["/catalog/bukety-iz-khrizantemy (37)"]
CAT --> C7["/catalog/svadebnye-bukety-nevesty (3)"]
CAT --> C8["/catalog/monobuket (23)"]
CAT --> C9["/catalog/kompozitsii (7)"]
CAT --> C10["/catalog/tsvety-v-yashchike (1)"]
CAT --> C11["/catalog/tsvety-v-korzine (6)"]
C1 --> PDP["/product/[slug] — 94 товара"]
PDP --> CART["/cart — корзина + промокод"]
CART --> CHECKOUT["/checkout — одна страница"]
CHECKOUT --> OK["/checkout/success/[orderId]"]
HOME --> AUTH["/login · /register"]
AUTH --> ACC["/account/orders"]
ACC --> ORD["/account/orders/[orderId]"]
HOME --> SRC["/search?query="]
HOME --> PAGES["/pages/[slug] — о компании, доставка, оплата, как заказать, возврат, контакты, b2b, вакансии, политика, FAQ"]
HOME --> ADMIN["/admin — дашборд (роль admin)"]
ADMIN --> AP["/admin/products"]
ADMIN --> AC["/admin/categories"]
ADMIN --> AO["/admin/orders"]
4. Модель данных
Расширяем модель SPEC.md §4 полями, которые требует источник (кросс-листинг, бейджи, старая цена, варианты, промокоды, контент). Ключевые дополнения выделены.
- Category — id, slug, name, description, position, parentId?,
visible(скрытые категории источника: розы/гортензии/тюльпаны — CATALOG.md §2). - Product — id, slug, name, description,
priceMinor(R2),oldPriceMinor?(старая цена),currency,badge(hit|new|sale|null),stock, status (draft|active|archived), createdAt,composition,stems?,heightCm?,care,deliveryTime(R9/R11). - ProductCategory — productId, categoryId (кросс-листинг: 256 слотов → 94 товара).
- ProductImage — productId, url, position.
- ProductVariant — productId, label («15 роз»), priceMinor, stock (у источника: 15/19/25 роз → 198/240/325 BYN).
- ProductFeature — productId, key («Кому дарим цветы»), value[] (характеристики/фасеты, R8).
- Cart / CartItem — productId, quantity, цена всегда из продукта.
- Customer / Address — email, passwordHash, name, addresses[].
- Order — number, customerId, status (
pending|paid|fulfilled|cancelled), subtotalMinor, shippingMinor,discountMinor, totalMinor, currency, shippingAddress (получатель, телефон,giftNote= текст открытки, deliveryDate/Time), promoCodeId?, createdAt. - OrderItem — orderId, productId, name, unitPriceMinor, quantity (иммутабельный снимок).
- Payment — orderId, provider, providerRef, status, amountMinor.
- PromoCode — code, type, value, activeFrom/To (
coupon_codeисточника). - ContentPage — slug, title, body (R11; FAQ — блоком или страницей).
- DeliveryOption — id, name (
todoor/pickup), priceMinor, freeOverMinor (R7).
erDiagram
CATEGORY {
int id PK
string slug
string name
int parentId FK
bool visible
}
PRODUCT {
int id PK
string slug
string name
int priceMinor
int oldPriceMinor
string currency
string badge
int stock
string status
string composition
int stems
int heightCm
}
PRODUCT_CATEGORY {
int productId FK
int categoryId FK
}
PRODUCT_IMAGE {
int id PK
int productId FK
string url
int position
}
PRODUCT_VARIANT {
int id PK
int productId FK
string label
int priceMinor
int stock
}
PRODUCT_FEATURE {
int id PK
int productId FK
string key
string value
}
CART {
int id PK
int customerId FK
}
CART_ITEM {
int id PK
int cartId FK
int productId FK
int quantity
}
CUSTOMER {
int id PK
string email
string passwordHash
string name
}
ADDRESS {
int id PK
int customerId FK
string street
}
ORDER {
int id PK
string number
int customerId FK
string status
int subtotalMinor
int shippingMinor
int discountMinor
int totalMinor
string currency
string giftNote
int promoCodeId FK
datetime createdAt
}
ORDER_ITEM {
int id PK
int orderId FK
int productId FK
string name
int unitPriceMinor
int quantity
}
PAYMENT {
int id PK
int orderId FK
string provider
string providerRef
string status
int amountMinor
}
PROMO_CODE {
int id PK
string code
string type
int value
}
CONTENT_PAGE {
int id PK
string slug
string title
string body
}
DELIVERY_OPTION {
string id PK
string name
int priceMinor
int freeOverMinor
}
CATEGORY ||--o{ CATEGORY : "parent of"
CATEGORY ||--o{ PRODUCT_CATEGORY : lists
PRODUCT ||--o{ PRODUCT_CATEGORY : "listed in"
PRODUCT ||--o{ PRODUCT_IMAGE : has
PRODUCT ||--o{ PRODUCT_VARIANT : has
PRODUCT ||--o{ PRODUCT_FEATURE : has
PRODUCT ||--o{ CART_ITEM : "added as"
PRODUCT ||--o{ ORDER_ITEM : "snapshot in"
CUSTOMER ||--o| CART : owns
CART ||--o{ CART_ITEM : contains
CUSTOMER ||--o{ ADDRESS : has
CUSTOMER ||--o{ ORDER : places
PROMO_CODE ||--o{ ORDER : discounts
ORDER ||--|{ ORDER_ITEM : contains
ORDER ||--o{ PAYMENT : "paid by"
5. Ключевые потоки
5.1 Витрина: каталог → корзина
flowchart LR
A["Каталог / фасеты"] --> B["Карточка товара"]
B --> C{"В наличии?"}
C -- нет --> D["Кнопка disabled + причина"]
C -- да --> E["POST /api/cart — добавить"]
E --> F["Бейдж корзины обновлён"]
F --> B
5.2 Чекаут и оплата
sequenceDiagram
actor C as Покупатель (вошёл)
participant UI as /checkout
participant API as API
participant PAY as PaymentProvider (mock)
participant DB as БД
C->>UI: Заполняет получателя, доставку, промокод
UI->>API: POST /api/checkout (idempotency-key)
API->>DB: Ревалидация цен и остатков серверно
alt Товар распродан
API-->>UI: 409 + какой товар недоступен
else Всё доступно
API->>DB: Создать Order (status=pending) + OrderItem (снимок)
API->>PAY: charge(amountMinor)
alt Оплата успешна
PAY-->>API: succeeded, providerRef
API->>DB: Order=paid, Payment=succeeded
API-->>UI: 201 {orderNumber}
UI-->>C: /checkout/success/[orderId]
else Оплата не прошла
PAY-->>API: failed
API->>DB: Order остаётся pending
API-->>UI: 402 + путь повтора
end
end
5.3 Админ: жизненный цикл товара и заказа
stateDiagram-v2
[*] --> draft: создать товар
draft --> active: опубликовать
active --> archived: архивировать
archived --> active: вернуть
note right of archived
Архивный товар исчезает из витрины,
но остаётся в прошлых заказах.
end note
stateDiagram-v2
[*] --> pending: заказ создан
pending --> paid: оплата подтверждена
pending --> cancelled: отклонён
paid --> fulfilled: выдан/доставлен
paid --> cancelled: возврат
6. Данные для наполнения (сиды из аудита)
- Категории: 11 видимых (+ 3 скрытых: розы/гортензии/тюльпаны, CATALOG.md §2).
- Товары: 94 уникальных с ценами, старыми ценами и бейджами (CATALOG.md §3); кросс-листинг
через
ProductCategory. - Варианты: у части товаров («N роз» → цена) —
ProductVariant. - Константы магазина: телефоны, e-mail, часы, адрес самовывоза, юрлицо, мессенджеры
(CATALOG.md §6) →
lib/store/constants.ts. - PDP-контракт R9 заполняется вручную/скриптом при переносе: состав, стебли, высота, уход, наличие, срок доставки (в источнике отсутствуют).
7. Слои сборки и приёмка (definition of done)
| # | Слой | Владелец | Проверка (команда / runtime) |
|---|---|---|---|
| 1 | План и рамки | Startup Creator | docs/PLAN.md + docs/CATALOG.md в репозитории; каждая карточка слоя содержит проверку |
| 2 | Диаграмма архитектуры | Diagram Expert | ERD/архитектура на /diagrams открываются без потерь; сущности = §4 |
| 3 | БД и формы | DataTables Expert | миграции + сид на чистом клоне; агрегатный запрос = пересчитанному вручную итогу |
| 4 | Бэкенд и API | Code Expert | npm run lint/typecheck + неавторизованный запрос к админ-маршруту → 401/403 |
| 5 | Фронтенд | Code Expert | npm run build + browse → cart → checkout → confirmation проходится на мок-провайдере |
| 6 | Админ-панель | Code Expert | нон-админ на /admin/* получает отказ серверно; смена статуса логируется |
| 7 | Control Panel | Control Panel Builder | виджеты показывают данные или пустое состояние; нет спиннера вечно; нет гориз. скролла 390×844 |
| 8 | Workflows | Elsa Workflows Manager | тестовый вебхук создаёт задачу и запись письма; неоплаченная ветка пропускает письмо |
| 9 | Качество и поставка | Code Expert | npm run ci (install + lint + typecheck + test + build) зелёный; README quickstart на чистом клоне |
| 10 | Деплой | Server Ops | публичный URL отдаёт приложение; рестарт контейнера восстанавливает сервис |
Полные критерии приёмки — SPEC.md §8.
8. Поэтапный порядок сборки
- Каркас Next.js: install → dev-server → пустая оболочка → коммит.
- Схема и миграции по §4, сид из §6.
- Каталог и карточка (витрина становится демонстрируемой).
- Корзина + чекаут + оплата (сначала мок).
- Аккаунт + история заказов.
- Админ-панель.
- Качество: тесты, CI, README,
.env.example, доступность.
9. Открытые вопросы (нужно решение пользователя)
| # | Вопрос | Варианты |
|---|---|---|
| Q1 | Валюта витрины на этом этапе | интерим RUB с константой (рекомендую) ↔ сразу включить BYN, если платформа позволит |
| Q2 | Скрытые категории источника | перенести как невидимые (рекомендую) ↔ удалить ↔ сделать видимыми |
| Q3 | SEO-слаги | сохранить 1:1 (рекомендую) ↔ новая схема + 301 |
| Q4 | Чат Bitrix24 | сохранить ↔ заменить на WhatsApp/Telegram ↔ убрать |
| Q5 | FAQ/блог | FAQ-страница на этом этапе (рекомендую) ↔ отложить блог |
| Q6 | Числовой ID /product/1997/ (404) |
выяснить живой URL у владельца ↔ исключить товар |
10. Вне рамок
Мультивендор-маркетплейс, подписки, склад/инвентарные интеграции, налоговые движки, мультиязычная витрина (SPEC.md §1); реальные платёжные шлюзы; новый визуальный дизайн на этом этапе; перенос аналитики и чата — только решение и закладки (R12).