la-rose/docs/PLAN.md
semianiakavy 262ae126fb docs: complete source audit, add catalog inventory and redesign plan
- 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
2026-10-04 17:49:41 +00:00

20 KiB
Raw Blame History

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. Поэтапный порядок сборки

  1. Каркас Next.js: install → dev-server → пустая оболочка → коммит.
  2. Схема и миграции по §4, сид из §6.
  3. Каталог и карточка (витрина становится демонстрируемой).
  4. Корзина + чекаут + оплата (сначала мок).
  5. Аккаунт + история заказов.
  6. Админ-панель.
  7. Качество: тесты, 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).