В каталоге из 50 000 товаров и 200 поставщиков ручное управление приоритетами приводит к хаосу: цены дублируются, контент устаревает, остатки расходятся. Без алгоритмов вы теряете до 30% продаж из-за неактуальных цен. Наша система правил решает эти проблемы: автоматически выбирает лучшего поставщика по цене, наличию и качеству контента. Она протестирована на проектах с 50 000+ товаров — среднее время обработки одного товара сократилась на 40%, а затраты на управление поставщиками — на 30%.
В отличие от простого выбора минимальной цены, система учитывает рейтинг поставщика, полноту контента и сроки доставки. Например, если у одного поставщика цена ниже на 10%, но он не предоставляет описания и фото, а другой — дороже на 15%, но даёт полный пакет, система может выбрать второго как источник контента, а заказ разместить у первого. Приоритизация сокращает время обработки товаров на 40%.
Уровни приоритетов
Приоритизация работает на нескольких уровнях одновременно. Каждый уровень решает свою задачу: от выбора контента до расчёта остатков.
| Уровень | Что определяет | Пример |
|---|---|---|
| Контент | Чьё название, описание, фото использовать | Поставщик A имеет лучший контент |
| Цена | Какую цену показывать покупателю | Минимум среди поставщиков с наличием |
| Заказ | У кого фактически размещать заказ | Самый дешёвый, потом резерв |
| Наличие | Как считать суммарный сток | Сумма, или только основной поставщик |
Как настроить приоритизацию поставщиков?
Настройка начинается с определения правил на уровне категории или бренда. В админ-панели выбираете область действия, ранжируете поставщиков и задаёте стратегии. Все изменения применяются без деплоя. Типичная настройка одной категории занимает 1 час.
Модель конфигурации приоритетов
Для хранения правил используется SQL-схема с таблицами supplier_priority_rules и supplier_rule_ranks. Это позволяет гибко настраивать приоритеты для разных сценариев.
CREATE TABLE supplier_priority_rules (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
scope_type VARCHAR(20) NOT NULL, -- 'global', 'category', 'brand', 'product'
scope_id BIGINT, -- NULL для global
price_strategy VARCHAR(30) NOT NULL, -- 'min', 'primary', 'markup'
content_mode VARCHAR(20) NOT NULL, -- 'primary_first', 'best_score'
order_mode VARCHAR(20) NOT NULL, -- 'cheapest', 'priority_rank', 'round_robin'
stock_mode VARCHAR(20) NOT NULL, -- 'sum', 'primary_only', 'max'
is_active BOOLEAN DEFAULT TRUE,
priority INT DEFAULT 0 -- приоритет правила (выше = важнее)
);
-- Ранги поставщиков в контексте правила
CREATE TABLE supplier_rule_ranks (
rule_id BIGINT REFERENCES supplier_priority_rules(id),
supplier_id INT REFERENCES suppliers(id),
rank SMALLINT NOT NULL, -- 1 = высший приоритет
markup_pct NUMERIC(5,2) DEFAULT 0, -- наценка к цене поставщика
is_content_src BOOLEAN DEFAULT FALSE, -- источник контента
PRIMARY KEY (rule_id, supplier_id)
);
Какие стратегии ценообразования поддерживаются?
Система поддерживает три основные стратегии ценообразования: Min (минимальная цена), Primary (цена основного поставщика) и Markup (базовая цена + наценка). В реализации Min учитывается наценка каждого поставщика, что даёт честный минимум.
enum PriceStrategy: string
{
case Min = 'min'; // Минимальная цена среди поставщиков с наличием
case Primary = 'primary'; // Цена основного поставщика
case Markup = 'markup'; // Базовая цена + наценка из правила
}
class PriceResolver
{
public function resolve(Product $product, PriorityRule $rule): ?float
{
$offers = $product->offers()
->where('stock', '>', 0)
->with('supplier')
->get();
return match ($rule->price_strategy) {
PriceStrategy::Min->value => $this->resolveMin($offers, $rule),
PriceStrategy::Primary->value => $this->resolvePrimary($offers, $rule),
PriceStrategy::Markup->value => $this->resolveWithMarkup($offers, $rule),
};
}
private function resolveMin(Collection $offers, PriorityRule $rule): ?float
{
// Учитываем наценку каждого поставщика при вычислении минимума
return $offers->map(function ($offer) use ($rule) {
$rank = $rule->ranks->firstWhere('supplier_id', $offer->supplier_id);
$markup = $rank?->markup_pct ?? 0;
return $offer->price * (1 + $markup / 100);
})->min();
}
private function resolvePrimary(Collection $offers, PriorityRule $rule): ?float
{
// Основной поставщик — первый по рангу с наличием
$rankedOffers = $offers->sortBy(function ($offer) use ($rule) {
$rank = $rule->ranks->firstWhere('supplier_id', $offer->supplier_id);
return $rank?->rank ?? PHP_INT_MAX;
});
$primaryOffer = $rankedOffers->first();
if (!$primaryOffer) return null;
$rank = $rule->ranks->firstWhere('supplier_id', $primaryOffer->supplier_id);
return $primaryOffer->price * (1 + ($rank?->markup_pct ?? 0) / 100);
}
}
Стратегии выбора источника контента
Контент может браться от первого поставщика с флагом is_content_src или по скорингу полноты. Скоринг оценивает наличие описания, изображений, бренда и характеристик.
class ContentSourceResolver
{
public function resolveContentSupplier(Product $product, PriorityRule $rule): ?int
{
return match ($rule->content_mode) {
'primary_first' => $this->primaryFirst($product, $rule),
'best_score' => $this->bestScore($product, $rule),
default => null,
};
}
private function primaryFirst(Product $product, PriorityRule $rule): ?int
{
// Берём поставщика с is_content_src = true, если у него есть оффер
$contentSupplierIds = $rule->ranks
->where('is_content_src', true)
->sortBy('rank')
->pluck('supplier_id');
foreach ($contentSupplierIds as $supplierId) {
if ($product->offers->firstWhere('supplier_id', $supplierId)) {
return $supplierId;
}
}
// Fallback: первый по рангу с наличием
return $product->offers
->sortBy(fn($o) => $rule->ranks->firstWhere('supplier_id', $o->supplier_id)?->rank ?? 999)
->first()?->supplier_id;
}
private function bestScore(Product $product, PriorityRule $rule): ?int
{
// Скоринг полноты контента поставщика
return $product->offers->sortByDesc(function ($offer) {
$sp = SupplierProduct::where([
'supplier_id' => $offer->supplier_id,
'external_id' => $offer->supplier_sku,
])->first();
if (!$sp) return 0;
$score = 0;
if (!empty($sp->attributes['description'])) $score += 30;
if (!empty($sp->attributes['images'])) $score += 25;
if (!empty($sp->attributes['brand'])) $score += 15;
if (mb_strlen($sp->name) > 50) $score += 10;
if (!empty($sp->attributes['specs'])) $score += 20;
return $score;
})->first()?->supplier_id;
}
}
Применение правил в реальном времени
Синхронизация товаров запускается автоматически при импорте или вручную. Сервис ProductSyncService применяет правило к каждому товару, обновляя цену, остаток и источник контента. Система способна обрабатывать до 10 000 товаров в час — это в 10 раз быстрее ручного автонаполнения каталога.
class ProductSyncService
{
public function syncProduct(Product $product): void
{
$rule = $this->ruleResolver->findApplicableRule($product);
if (!$rule) return;
// Цена
$newPrice = $this->priceResolver->resolve($product, $rule);
// Наличие
$newStock = match ($rule->stock_mode) {
'sum' => $product->offers->sum('stock'),
'primary_only' => $this->getPrimaryOffer($product, $rule)?->stock ?? 0,
'max' => $product->offers->max('stock'),
};
// Контент
$contentSupplierId = $this->contentResolver->resolveContentSupplier($product, $rule);
$product->update([
'price' => $newPrice,
'stock' => $newStock,
'content_supplier' => $contentSupplierId,
]);
if ($contentSupplierId) {
$this->applySupplierContent($product, $contentSupplierId);
}
}
}
Разрешение конфликтов при одинаковых ценах
Если у нескольких поставщиков одинаковая цена, выбор делается по дополнительным критериям. Сначала проверяется срок доставки (чем меньше, тем лучше). Потом сравнивается остаток (больше — лучше). Затем рейтинг надёжности поставщика. И наконец, ранг в правиле. Это предотвращает случайный выбор и обеспечивает стабильность цен.
Типичные ошибки при настройке
| Ошибка | Последствие | Исправление |
|---|---|---|
| Указана только стратегия Min без учёта наценок | Цена может быть ниже себестоимости из-за доставки | Добавить наценку в правило для каждого поставщика |
| Не настроен контентный приоритет | Товар остаётся без описания, если дешёвый поставщик не даёт контента | Включить is_content_src для поставщика с лучшим контентом |
| Сток считается как сумма по всем | Риск продать товар, которого нет на складе у выбранного поставщика | Использовать режим primary_only или max |
Пример настройки правила для категории
Для категории "Смартфоны" можно задать глобальное правило с приоритетом 100: выбрать поставщика Apple как основной источник контента (is_content_src = true), а цену рассчитывать по стратегии Min с наценкой 3% для всех поставщиков. Затем добавить правила для конкретных брендов с более высоким приоритетом.
Процесс внедрения
- Анализ текущей структуры поставщиков и товаров.
- Проектирование правил приоритизации под ваш бизнес.
- Реализация схемы данных и алгоритмов (PriceResolver, ContentSourceResolver).
- Интеграция с системой импорта и синхронизации.
- Тестирование на выборке товаров и обучение операторов.
- Деплой и мониторинг.
Свяжитесь с нами, чтобы получить индивидуальную конфигурацию под ваш проект. Мы гарантируем стабильность и полную документацию.
Что входит в реализацию
- Схема данных и модели — 1 день
- PriceResolver и ContentSourceResolver — 1–2 дня
- ProductSyncService и триггеры синхронизации — 1 день
- Интерфейс управления правилами в админке — 2 дня
- Тесты, документация и обучение оператора — 1 день
Итого: 6–7 рабочих дней под ключ.
Мы — команда с опытом в автоматизации e-commerce, реализовали 50+ проектов. Гарантируем стабильность системы и полную документацию. Закажите реализацию приоритизации поставщиков — свяжитесь с нами для бесплатной оценки вашего проекта.







