Каталог продуктов с вариантами и наборами: схема и UI‑паттерны
Спроектируйте каталог продуктов с вариантами и наборами: ясные правила SKU, логика запасов и UI‑паттерны, которые предотвращают недопустимые комбинации и оверселлинг.

Почему варианты и наборы быстро запутываются
Каталог кажется простым, когда каждый продукт — это один товар с одной ценой и одним количеством на складе. Добавьте цвет, размер, длительность подписки или упаковку для разных регионов — и эта простота ломается. Одна таблица «Products» уже не отвечает на базовые вопросы: что именно мы продаём и как это отслеживать?
Покупатели тоже ожидают точности. Они хотят выбирать опции, сразу видеть правильную цену и понимать, можно ли отправить выбранный товар сегодня. Если на странице написано «В наличии», а выбранный размер отсутствует, доверие падает быстро. Если цена меняется только при оформлении заказа — ждите тикетов в поддержку и возвратов.
Наборы добавляют второй уровень сложности, потому что выглядят как продукты, но ведут себя как правила. «Стартовый набор» может включать бутылку, помпу и набор фильтров. Его доступность зависит от частей, а ваши затраты и отчёты всё равно должны быть понятными.
Признаки, что каталог начинает трещать по швам:
- Вы создаёте дублирующие SKU только чтобы представить опцию.
- Количества на складе кажутся неправильными, особенно после продаж наборов.
- Экран админа превращается в длинный список почти одинаковых позиций.
- Скидки и налоги работают для одиночных товаров, но ломаются для комплектов.
- Отчёты не отвечают на вопрос «что именно было продано?»
Исправление в основном требует дисциплины: модель данных, остающаяся консистентной, и UI‑паттерны, которые делают выбор опций и доступность очевидными для клиентов и вашей команды.
Простыми словами: опции, варианты, SKU, наборы
Когда говорят «варианты», часто смешивают несколько понятий. Разобраться с терминами на ранней стадии упрощает дальнейшие решения (схема, UI, инвентарь).
Большинство команд используют такие определения:
- Опция: выбор, который делает покупатель, например Size или Color.
- Значение опции: одно значение внутри опции, например Size = M или Color = Black.
- Вариант: точная комбинация значений опций, например Size M + Color Black.
- SKU: продаваемая единица, которую вы отслеживаете в операциях и инвентаре. Вариант часто соответствует одному SKU, но не всегда.
- Bundle / kit / multipack: продукт, собранный из других продуктов. Bundle — маркетинговая группа (части можно продавать по отдельности). Kit — «обязательно отправляется вместе». Multipack — повторение одного и того же товара (например, набор из 3 пар носков).
ID тоже путают на практике. Внутренний ID — то, что использует база данных. SKU — операционный код (для комплектовки, пополнения и отчётов). Штрихкод (UPC/EAN) — то, что считывают сканеры. Один SKU может иметь несколько штрихкодов (для разных регионов), а у некоторых товаров штрихкода вообще нет.
Хорошее правило для решения, считать ли что‑то продаваемым вариантом: если оно может иметь другую цену, запас, вес или правила доставки — относите это к продаваемому варианту. Одна и та же футболка в размерах M и XL обычно требует отдельных учётов запасов, поэтому это отдельные SKU.
Решите, что должен поддерживать ваш каталог
Перед тем как выбирать схему, начните с вопросов, на которые бизнес должен отвечать ежедневно. Когда кто‑то смотрит товар, можете ли вы уверенно сказать: доступен ли он прямо сейчас, сколько стоит, как он будет отправлен и какие правила налога к нему применимы?
Полезный подход — решить, где хранится каждое «фактическое» значение. Общие факты — на уровне продукта, изменяющиеся факты — на уровне варианта (SKU). Если смешивать их, вы будете исправлять одну и ту же ошибку в двух местах.
Вопросы, которые обычно решают, какое поле относится к продукту, а какое — к варианту:
- Если поле меняется по размеру/цвету/материалу — оно на уровне варианта.
- Если верно для всех комбинаций опций — оно на уровне продукта.
- Если вы отчитываетесь по SKU (продажи, маржа, возвраты) — храните на уровне варианта.
- Если операционная логика использует это для подбора/упаковки/отправки — храните там, где работает склад: на SKU.
- Если значение можно безопасно вывести (например, отображаемое имя из значений опций), храните только исходные данные и генерируйте отображение.
Держите один источник правды. Например, не храните «цену» и на продукте, и на варианте, если роли не разграничены (например, на продукте MSRP, на варианте — фактическая цена продажи).
Планируйте изменения. Возможно, позже вы добавите новую опцию (например, Length), выведете вариант из продажи или объедините SKU после смены поставщика. Это проще, когда варианты — полноценные записи, а не просто метки.
Если вы строите в AppMaster, понятные имена сущностей в Data Designer облегчают поддержание при изменении требований.
Практичная схема для продуктов и вариантов
Чистая схема помогает каталогу оставаться понятным, когда простой продукт превращается в десятки продаваемых комбинаций. Цель — моделировать выборы (что выбирает покупатель) отдельно от продаваемых единиц (что вы реально храните и отправляете).
Надёжный набор сущностей:
- Product: родительский товар (название, описание, бренд, категория, изображения по умолчанию)
- Option: тип выбора (Size, Color)
- OptionValue: допустимые значения (Small, Medium, Red, Blue)
- Variant: продаваемая единица (одна комбинация значений)
- VariantOptionValue: таблица связи, связывающая Variant с его OptionValue
Уникальность варианта — место, где многие каталоги допускают ошибки. Самый безопасный подход — нормализованный: обеспечьте по одному OptionValue на Option для каждого Variant и предотвращайте дублирующие комбинации. Если нужна скорость, храните вычисляемый «variant key», например color=red|size=m (или его хеш) на Variant и обеспечьте уникальность в рамках Product.
Держите поля, которые меняются по комбинации, в Variant, а не в Product: SKU, штрихкод, цена, compare‑at price, себестоимость, вес, размеры, статус (активен/снято с производства) и изображения (одна основная или небольшой набор).
Для пользовательских атрибутов (например, «материал» или «инструкции по уходу») не добавляйте бесконечно новые колонки. Поле JSONB на Product или Variant хорошо работает в PostgreSQL в паре с небольшой валидацией в приложении.
Правила SKU, которые остаются стабильными со временем
SKU — это идентификатор продаваемой единицы, который вы обещаете держать стабильным. Он должен отвечать на один вопрос: «Какой именно товар мы продали?» Не стоит перегружать SKU маркетинговыми названиями, полным текстом опций, сезоном или историей. Если его перегрузить, потом всё сложно менять без ломки отчётов.
Решите заранее, будут ли SKU присваиваться вручную или генерироваться. Ручные SKU безопаснее, если у вас уже есть ERP, штрихкоды или SKU поставщиков, которые должны совпадать. Генерируемые SKU удобнее при большом количестве вариантов, но только если правила генерации не поменяются посередине года. Частый компромисс — фиксированный базовый SKU, который вы контролируете, плюс короткий сгенерированный суффикс для атрибутов варианта.
Правила, которые остаются читаемыми и переживают рост:
- Держите SKU стабильными после того, как по нему сделан заказ. Никогда не «переименовывайте» старые SKU.
- Отделяйте внутренний ID от SKU. SKU — для людей, ID — для баз данных.
- Используйте короткие префиксы для семейств продуктов (например, TSH, MUG), а не полные слова.
- Избегайте значений, которые могут поменяться (например, «2026» или «SUMMER»), если только бизнес действительно так не работает.
Снятые с производства SKU не должны удаляться. Помечайте их неактивными, храните историю цен и оставляйте их видимыми в прошлых заказах, возвратах и отчётах. Если вы заменяете SKU, храните ссылку «заменён на», чтобы поддержка могла проследить изменения.
Правила валидации предотвращают медленный ущерб: обеспечьте уникальность SKU среди всех продаваемых позиций, разрешайте только буквы, цифры и дефисы, задайте длину (часто 20–32 символа), и зарезервируйте префиксы для специальных случаев (например, «BND-» для наборов). В AppMaster эти проверки удобно реализуются как ограничения данных плюс Business Process, который блокирует сохранение при нарушении правил.
Логика инвентаря — больше, чем просто в наличии/нет
Инвентарь путается, когда один «продукт» может означать множество продаваемых единиц. Прежде чем писать правила, выберите единицу учёта запасов: вы отслеживаете на уровне продукта, варианта или и того, и другого?
Если покупатели выбирают размер или цвет, обычно безопаснее отслеживать запасы по вариантам. Футболка может быть «в наличии» в целом, но вариант Small‑Blue уже распродан. Продуктовый уровень подходит для того, где варианты не меняют то, что вы физически храните (например, цифровая лицензия с разными планами биллинга). Некоторые команды хранят оба уровня: продуктовый для отчётов, вариантный — для продаж.
Предотвращение оверселлинга — не магический флаг, а набор чётких состояний. Большинству каталогов нужны резервы (удержание единиц для корзин или неоплаченных заказов), бэко́рдера (разрешать продажу с явной датой отгрузки), страховые запасы (скрытое количество на случай задержек синхронизации) и атомарные обновления (уменьшение запаса в той же операции, что подтверждает заказ).
Краевые случаи — откуда берётся «тайный запас». Возвраты должны добавлять товар обратно только после проверки, а не при создании транспортной этикетки. Повреждённые позиции переводите в отдельный статус или местоположение, чтобы они не выглядели продаваемыми. Изменения запасов должны вести аудит (кто, что и почему изменил), особенно если несколько каналов обновляют инвентарь.
Наборы и комплекты вносят одно ключевое правило: не уменьшайте запись «набора», если набор — просто группа. Уменьшайте компонентные позиции. 3‑пак должен уменьшать три единицы одного и того же SKU; кит — по одной единице каждого компонента.
Пример: «Стартовый набор» включает 1 бутылку и 2 фильтра. Если у вас 10 бутылок и 15 фильтров, доступность набора равна 7, потому что фильтры ограничивают наличие. Компонентная математика сохраняет согласованность в отчётах, возвратах и пополнении.
В AppMaster это естественно мапится на таблицы вариантов в Data Designer и логику резервирования/уменьшения в Business Process Editor, чтобы каждое оформление заказа следовало одним и тем же правилам.
Моделирование наборов и комплектов без повреждения отчётности
Наборы — место, где каталоги часто уходят в спецкейсы. Проще всего моделировать наборы как обычные продаваемые товары и прикреплять к ним явный список компонентов.
Чистая структура: Product (продаваемая единица) плюс BundleLines. Каждая BundleLine указывает на компонентный SKU (или на продукт‑компонент с требуемым вариантом) и хранит количество. Заказы по-прежнему показывают «один товар», но вы можете развернуть его в части, когда нужны затраты, движение запасов и детали комплектации.
Большинство наборов попадают в одну из категорий:
- Fixed bundle (kit): всегда одинаковые компоненты и количества.
- Configurable bundle: покупатель выбирает из разрешённых компонентов (всё равно сохраняется как строки в заказе).
- Virtual bundle: маркетинговая группа (часто без эффекта на инвентарь), полезна для мерчандайзинга без усложнения логики выполнения.
Ценообразование — место, где команды случайно ломают отчёты. Решите заранее, что отображается в строке заказа и что питает отчёты по марже и инвентарю. Частые подходы: фиксированная цена набора, сумма частей или дисконтированная сумма с правилами на компонент (например, «выбери любые 3 товара, самый дешёвый — со скидкой 50%»).
Инвентарь должен быть не менее строгим: кит доступен только если все требуемые компоненты доступны в нужном количестве. Для отчётности храните два представления продажи:
- Проданный товар: SKU набора (для выручки, конверсии и мерчандайзинга).
- Потреблённые компоненты: развёрнутые BundleLines (для движения запасов, COGS и путей комплектации).
В AppMaster это легко реализовать: таблица Bundle и строки BundleLine, с Business Processes, которые при оформлении заказа разворачивают компоненты и записывают и продажу набора, и расход компонентов в одной транзакции.
UI‑паттерны для выбора опций и вариантов
Хороший интерфейс опций помогает людям двигаться дальше. Плохой интерфейс заставляет их догадываться, получать ошибки и уходить. Главное — привести покупателя к реальному, покупаемому варианту как можно раньше, показывая при этом, что именно меняется от выбора.
Для покупателей: сначала опции, потом варианты
Надёжный паттерн — показать опции (Size, Color, Material), затем вычислять и показывать только те варианты, которые ещё имеют смысл.
Вместо того, чтобы разрешать пользователю выбирать любую комбинацию и получать ошибку при добавлении в корзину, делайте невозможные сочетания недоступными сразу после выбора. Например, если выбран Color = Black, размеры, которых нет в Black, должны стать выключенными (не удалёнными), чтобы клиент понял, что именно недоступно.
По мере изменения выбора обновляйте то, что важно: цену (включая распродажную цену и любые скидки для набора), основное изображение (подходящее под цвет или стиль), статус запаса (точный запас варианта, а не общий запас продукта), срок доставки (если зависит от варианта) и, при желании, SKU или «код товара» для поддержки.
Держите интерфейс спокойным. Показывайте несколько групп опций за раз, избегайте огромных селектов там, где подходят свачи или кнопки, и делайте текущий выбор очевидным.
Для админа: редактируйте варианты как в таблице
В админке людям нужна скорость, а не красивые карточки. Грид вариантов подходит хорошо: каждая строка — SKU, каждый столбец — опция или правило (цена, штрихкод, вес, запас, активность). Добавьте массовые операции для частых задач: обновление цен, переключение доступности, присвоение изображений.
Если вы строите это в AppMaster, практичная настройка — грид, привязанный к таблице Variant с фильтрами (значение опции, активность, низкий запас) и действием массового обновления, которое валидирует правила перед сохранением.
Пошагово: выбор варианта и проверка доступности набора
Поток выбора должен казаться простым, но под капотом нужны строгие правила. Цель — всегда знать, какой точный SKU конфигурирует покупатель, и можно ли его купить прямо сейчас.
Выбор варианта (один продукт)
Загружайте больше, чем имя продукта и изображения. Нужен полный набор значений опций (Size, Color) и список валидных комбинаций вариантов, которые существуют как SKU.
Надёжный поток:
- Получите продукт, его опции и все существующие варианты (или компактную карту валидных комбинаций).
- Храните текущие выборы покупателя (например: Size=M, Color=Black) и обновляйте их при каждом клике.
- После каждого изменения находите совпадающий вариант, сравнивая выбранные значения опций со списком вариантов.
- Если совпадение есть и он покупаем (активен, цена задана, не заблокирован), включайте кнопку «Добавить в корзину».
- Если совпадения нет, держите кнопку отключённой и подсказывайте покупателю, как выбрать валидный вариант.
Небольшая деталь UI, которая предотвращает фрустрацию: делайте невозможные значения опций недоступными сразу после выбора предыдущих опций. Если Size=M существует только в Black, другие цвета показывайте как недоступные после выбора M.
Доступность набора (кит из компонентов)
Для наборов «в наличии» — не одно число. Всё зависит от компонентов. Обычное правило:
bundle_available = minimum over components of floor(component_stock / component_qty_per_bundle)
Пример: «Стартовый набор» требует 1 бутылку и 2 фильтра. Если у вас 10 бутылок и 9 фильтров, доступность набора = min(floor(10/1)=10, floor(9/2)=4) = 4 набора.
Сообщения об ошибках должны быть конкретными. Лучше «Доступно только 4 набора (ограничено по фильтрам)» вместо «Нет в наличии». В AppMaster такую проверку просто выразить в Business Process: сначала вычислить подходящий вариант, затем лимиты по набору, и вернуть понятный статус для UI.
Частые ошибки и ловушки
Каталоги ломаются, когда вы моделируете «всё, что может существовать», вместо «то, что вы действительно будете продавать». Самый быстрый путь в тупик — сгенерировать все возможные комбинации опций заранее, а затем пытаться поддерживать порядок по мере роста каталога.
Создание вариантов, которые никогда не будут в продаже — классическая ловушка. Если у вас 4 цвета × 6 размеров × 3 материалов = 72 варианта, но реально будут продаваться только 10, остальные 62 записи создают шум, приводят к ошибкам и замедляют отчёты.
Ценообразование — тихий источник багов. Проблемы начинаются, когда цена хранится в нескольких местах (product, variant, price table, promo table) без единого источника правды. Простое правило: храните базовую цену в одном месте и делайте переопределения только там, где это действительно нужно.
Наборы и комплекты дают провалы в инвентаре, когда вы уменьшаете и запись набора, и его компоненты. Если вы продаёте «Стартовый набор», включающий 1 бутылку и 2 фильтра, и уменьшаете 1 набор плюс ещё 1 бутылку и 2 фильтра — запас уйдёт в ноль слишком рано. Держите набор как продаваемую единицу, но вычисляйте доступность и списание с его компонентов.
Инструменты админа могут нанести вред, если позволяют вводить некорректные данные. Добавьте предохранители рано:
- Блокируйте дублирующие SKU, даже среди архивированных позиций.
- Требуйте, чтобы у каждого варианта были все значения опций (никаких «размер отсутствует»).
- Запрещайте двум вариантам иметь одинаковую комбинацию опций.
- Валидируйте компоненты наборов (никаких нулевых количеств, никаких отсутствующих SKU).
И, наконец, планируйте изменения. Добавление новой опции позже (например, «Width» для обуви) — это миграция, а не простая галочка. Решите, что происходит с существующими вариантами, как задаются значения по умолчанию и как старые заказы сохраняют свой оригинальный SKU и снимок опций.
Быстрая проверка перед запуском
Перед релизом прогоните вещи, которые ломаются в реальной жизни: поиск SKU, обновление запасов и блокировка невозможных покупок.
Убедитесь, что каждый продаваемый SKU легко найти. Поиск должен находить по имени, коду SKU, штрихкоду/GTIN и ключевым атрибутам (например, размер или цвет). Если вы используете сканирование на складе, протестируйте несколько физических сканов и подтвердите, что они ссылаются на точный SKU, а не только на родительский продукт.
Будьте строги относительно того, где происходят изменения запасов. Выберите один источник правды (логику движения запасов) и убедитесь, что каждое событие его использует: заказы, отмены, возвраты, ручные корректировки и сборка наборов. Если запасы можно менять в двух экранах или двух workflow — дрейф гарантирован.
Проверки перед запуском, которые стоит выполнить:
- Выберите опции в UI и подтвердите, что недопустимые комбинации блокируются на этапе выбора (до add‑to‑cart), а не обнаруживаются на оформлении заказа.
- Для наборов подтвердите, что доступность определяется самым дефицитным компонентом и корректной кратностью (2 батарейки в наборе должны уменьшать доступность вдвое).
- Снимите SKU с продажи и проверьте, что он исчезает из каталога и поиска, но корректно отображается в прошлых заказах, счетах и отчётах.
- Разместите тестовый заказ, отмените его, затем разместите снова; запас должен вернуться и зарезервироваться корректно.
Если вы строите в AppMaster, держите логику обновления запасов в одном Business Process и вызывайте его из всех мест. Эта простая привычка предотвращает большинство ошибок с инвентарём.
Пример сценария и практичные шаги
Представьте магазин одежды, где ещё не налажен серьёзный каталог.
Вы продаёте футболку с двумя опциями: Size (S, M, L) и Color (Black, White). Каждая покупаемая комбинация — отдельный вариант с собственным SKU, ценой (если нужно) и запасом.
В схеме храните одну запись Product для «Classic T‑shirt», две записи Option (Size, Color) и несколько Variant. Каждый Variant хранит свои значения опций (например: Size=M, Color=Black) и SKU (например: TSH-CL-M-BLK). Инвентарь отслеживается на уровне Variant, а не Product.
В UI сузьте выбор и предотвращайте тупиковые состояния. Чистый паттерн: показывайте все цвета сначала, а затем только те размеры, которые существуют для выбранного цвета (или наоборот). Если «White + L» отсутствует, не позволяйте выбрать его или показывайте его как отключённый с пояснением.
Теперь добавьте набор: «Gift Set», который включает:
- 1x Classic T‑shirt (любой вариант)
- 1x Sticker pack (простой SKU)
Доступность набора ограничена наиболее дефицитным компонентом. Если Sticker pack в запасе 5 штук, вы не можете продать больше 5 наборов, даже если футболок много. Если набор требует конкретного варианта футболки (например, Black M), то доступность равна min(StickerPackStock, BlackMStock).
Практичные следующие шаги в AppMaster:
- Постройте таблицы в Data Designer (Products, Options, OptionValues, Variants, VariantOptionValues, Inventory, Bundles, BundleComponents).
- Реализуйте «find valid variants» и «calculate bundle availability» в Business Process Editor.
- Сгенерируйте веб и нативные мобильные интерфейсы из того же проекта, используя одинаковые правила фильтрации вариантов и доступности во всех местах.
Если нужен быстрый прототип end‑to‑end, AppMaster (appmaster.io) предназначен для создания полноценных приложений с реальной бизнес‑логикой — а это ровно то, от чего зависят выбор вариантов и правила инвентаря.


