projects/org/@biz-lideravto/it/docs/arh/COMPATIBILITY_SOLUTION.md

Решение: Совместимость деталей и организация каталога

Проект: lider-drupal
Дата: 2026-02-12
Проблема: Как определить совместимость деталей между моделями


ПРОБЛЕМА

У нас есть три источника данных, которые нужно связать:

┌──────────────────────────────────────────────────────────────┐
│ 1. BAZON CSV                                                  │
│    Модель: "4-FH", "4-FM", "6-R"                             │
│    БМ_0: "4-FM" (дополнительная модель)                      │
│    БМ_1: "6-R" (ещё одна)                                    │
│                                                               │
│ 2. РЕАЛЬНЫЕ МОДЕЛИ (NocoDB)                                  │
│    Volvo FH4 2012, Volvo FM4 2012                            │
│    Scania R-6 2010, Scania G-6 2010                          │
│                                                               │
│ 3. КАК ИЩУТ ЛЮДИ (поведение)                                 │
│    "запчасти volvo фш4"                                       │
│    "фара вольво 4 фш"                                         │
│    "volvo fh 2012"                                            │
└──────────────────────────────────────────────────────────────┘

Вопрос: Как связать эти три источника и определить совместимость?


РЕШЕНИЕ: Три уровня организации

Уровень 1: База данных (NocoDB) — ИСТОЧНИК ИСТИНЫ

┌─────────────────────────────────────────────────────────────┐
│ Таблица: Модели                                              │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│ id | name          | old_code | generation | platform      │
│────┼───────────────┼──────────┼────────────┼───────────────┤
│ 1  | Volvo FH4     | 4-FH     | v4         | euro6         │
│ 2  | Volvo FM4     | 4-FM     | v4         | euro6         │
│ 3  | Volvo FH3     | 3-FH     | v3         | euro5         │
│ 4  | Volvo FM3     | 3-FM     | v3         | euro5         │
│────┼───────────────┼──────────┼────────────┼───────────────┤
│ 10 | Scania R-6    | 6-R      | s6         | pgr           │
│ 11 | Scania G-6    | 6-G      | s6         | pgr           │
│ 12 | Scania P-6    | 6-P      | s6         | pgr           │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ Таблица: Узлы                                                │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│ id | name      | code      | aggregate_id | compat_type    │
│────┼───────────┼───────────┼──────────────┼────────────────┤
│ 1  | Оптика    | optics    | 1 (Кабина)   | model          │
│ 2  | Подвеска  | suspension| 2 (Шасси)    | platform       │
│ 3  | Турбина   | turbo     | 3 (Силовой)  | engine         │
└─────────────────────────────────────────────────────────────┘

Уровень 2: Drupal Taxonomy — СПРАВОЧНИКИ

Импорт из NocoDB → Drupal:

// Скрипт импорта справочников
// drush scr import-models.php

use Drupal\taxonomy\Entity\Term;

// 1. Импорт моделей из NocoDB
$nocodb_models = getNoCDBModels(); // API call

foreach ($nocodb_models as $row) {
    $term = Term::create([
        'vid' => 'models',
        'name' => $row['name'],  // "Volvo FH4"
        'field_old_code' => $row['old_code'],  // "4-FH"
        'field_generation' => $row['generation'],  // "v4"
        'field_platform' => $row['platform'],  // "euro6"
        'field_nocodb_id' => $row['id'],  // 1
    ]);
    $term->save();
}

Результат в Drupal:

taxonomy/models:
  - tid:100, name:"Volvo FH4", old_code:"4-FH", platform:"euro6"
  - tid:101, name:"Volvo FM4", old_code:"4-FM", platform:"euro6"
  - tid:102, name:"Scania R-6", old_code:"6-R", platform:"pgr"

Уровень 3: Commerce Product — ТОВАРЫ

Импорт CSV → Drupal с определением совместимости:

// ЭТАП 1: Парсинг CSV и определение моделей

function parseCompatibility($csv_row) {
    // Из CSV:
    // Модель: "4-FH"
    // БМ_0: "4-FM"
    // БМ_1: "6-R"

    $old_codes = [
        $csv_row['Модель'],  // "4-FH"
        $csv_row['БМ_0'],    // "4-FM"
        $csv_row['БМ_1'],    // "6-R"
    ];

    // Убрать пустые
    $old_codes = array_filter($old_codes);

    // Найти taxonomy terms по old_code
    $model_terms = [];
    foreach ($old_codes as $code) {
        $terms = \Drupal::entityTypeManager()
            ->getStorage('taxonomy_term')
            ->loadByProperties([
                'vid' => 'models',
                'field_old_code' => $code,
            ]);

        if (!empty($terms)) {
            $model_terms[] = reset($terms);
        }
    }

    return $model_terms;
    // → [tid:100 (Volvo FH4), tid:101 (Volvo FM4), tid:102 (Scania R-6)]
}

Создание Product:

function createProduct($csv_row) {
    $oem = $csv_row['OEM'];

    // 1. Определить совместимые модели
    $models = parseCompatibility($csv_row);

    // 2. Определить узел
    $node = determineNode($csv_row['Наименование полное']);

    // 3. Узел → агрегат → тип совместимости
    $aggregate = $node->field_aggregate->entity;
    $compat_type = $aggregate->field_compatibility_type->value;

    // 4. Расширить совместимость по типу
    $models = expandCompatibility($models, $compat_type);

    // 5. Создать Product
    $product = Product::create([
        'title' => generateTitle($csv_row),
        'field_oem' => $oem,
        'field_models' => $models,  // ← здесь все совместимые модели
        'field_node' => $node,
        'field_aggregate' => $aggregate,
    ]);

    $product->save();
}

АЛГОРИТМ: Расширение совместимости

/**
 * Расширяет список моделей на основе типа совместимости
 */
function expandCompatibility($base_models, $compat_type) {
    $result = [];

    switch ($compat_type) {

        case 'model':
            // MODEL: только указанные модели
            // Фара FH4 НЕ подходит к FM4
            return $base_models;

        case 'platform':
            // PLATFORM: все модели той же платформы
            // Амортизатор FH4 = FM4 (euro6)
            foreach ($base_models as $model) {
                $platform = $model->field_platform->value;

                // Найти все модели с той же платформой
                $siblings = \Drupal::entityTypeManager()
                    ->getStorage('taxonomy_term')
                    ->loadByProperties([
                        'vid' => 'models',
                        'field_platform' => $platform,
                    ]);

                $result = array_merge($result, $siblings);
            }
            break;

        case 'engine':
            // ENGINE: все модели с тем же двигателем
            // Турбина D13K → все модели с D13K
            foreach ($base_models as $model) {
                $engine = extractEngine($model);  // "D13K"

                $models_with_engine = \Drupal::entityTypeManager()
                    ->getStorage('taxonomy_term')
                    ->loadByProperties([
                        'vid' => 'models',
                        'field_engines' => $engine,  // JSON field
                    ]);

                $result = array_merge($result, $models_with_engine);
            }
            break;

        case 'generation':
            // GENERATION: все модели того же поколения
            // Блок ECU FH4 = FM4 (v4)
            foreach ($base_models as $model) {
                $generation = $model->field_generation->entity;

                $models_in_gen = \Drupal::entityTypeManager()
                    ->getStorage('taxonomy_term')
                    ->loadByProperties([
                        'vid' => 'models',
                        'field_generation' => $generation->id(),
                    ]);

                $result = array_merge($result, $models_in_gen);
            }
            break;

        case 'universal':
            // UNIVERSAL: широкая совместимость
            // Масляный фильтр → все модели марки
            foreach ($base_models as $model) {
                $brand = $model->field_generation->entity->field_brand->entity;

                $all_brand_models = getBrandModels($brand);
                $result = array_merge($result, $all_brand_models);
            }
            break;
    }

    // Убрать дубли
    return array_unique($result, SORT_REGULAR);
}

ПРИМЕРЫ РАБОТЫ

Пример 1: Фара Volvo (MODEL тип)

CSV:

Модель: 4-FH
БМ_0: 4-FM
Наименование: Фара правая

Шаги:

  1. Парсинг:
    - 4-FH → tid:100 (Volvo FH4)
    - 4-FM → tid:101 (Volvo FM4)

  2. Определение узла:
    - "Фара правая" → Оптика → Агрегат "Кабина" → compat_type = model

  3. Расширение:
    - Тип model → НЕ расширяем
    - Результат: [FH4, FM4] — только указанные

  4. Product:
    yaml field_models: [tid:100, tid:101] field_compatibility: "Volvo FH4, FM4 (2012+)"

Почему FM4? Хотя фары РАЗНЫЕ, в BAZON их указали как совместимые (БМ_0). Возможно, физически подходят.


Пример 2: Амортизатор Volvo (PLATFORM тип)

CSV:

Модель: 4-FH
БМ_0: (пусто)
Наименование: Амортизатор передний

Шаги:

  1. Парсинг:
    - 4-FH → tid:100 (Volvo FH4)

  2. Узел:
    - "Амортизатор" → Подвеска → Агрегат "Шасси" → compat_type = platform

  3. Расширение (КЛЮЧЕВОЕ):
    ```php
    platform = "euro6"

SELECT * FROM models WHERE platform = "euro6"
→ Volvo FH4, Volvo FM4
```

  1. Product:
    yaml field_models: [tid:100, tid:101] ← автоматически добавлен FM4 field_compatibility: "Volvo FH4, FM4 (euro6)"

Результат: Даже если в CSV только FH4, система АВТОМАТИЧЕСКИ добавит FM4 (та же платформа).


Пример 3: Турбина (ENGINE тип)

CSV:

Модель: 4-FH
Наименование: Турбина D13K

Шаги:

  1. Парсинг:
    - 4-FH → tid:100 (Volvo FH4)

  2. Узел:
    - "Турбина D13K" → Турбонаддув → Агрегат "Силовой" → compat_type = engine

  3. Извлечение двигателя:
    php extractEngine("Турбина D13K") → "D13K"

  4. Расширение:
    php SELECT * FROM models WHERE engines CONTAINS "D13K" → Volvo FH4 (D13K), FM4 (D13K), FH3 (D13K)

  5. Product:
    yaml field_models: [tid:100, tid:101, tid:102] field_engine_type: "D13K" field_compatibility: "Volvo двигатель D13K (все модели)"


ОРГАНИЗАЦИЯ КАТАЛОГА НА САЙТЕ

Вариант А: Древовидная структура (классика)

/zapchasti/
  /volvo/
    /fh4/
      /optics/       ← Фильтр: model=FH4 AND node=optics
      /suspension/   ← Фильтр: model=FH4 AND node=suspension
      /engine/
    /fm4/
      /optics/
      /suspension/
  /scania/
    /r-6/
      /optics/

Проблема: Дублирование товаров.
- Амортизатор показан и в /volvo/fh4/suspension/, и в /volvo/fm4/suspension/

Решение: Canonical на Product.
- Canonical: /zapchasti/amortizator-20499340/
- Категория FH4: index, follow
- Категория FM4: index, follow
- Оба ведут на один товар


Вариант Б: Динамические категории (рекомендуется)

/zapchasti/
  /{brand}/{model}/             Все запчасти модели
  /{brand}/{model}/{node}/      Фильтр по узлу
  /{node}/                      Все детали узла (все марки)
  /{aggregate}/                 Все детали агрегата

Views для каждой страницы:

# /volvo/fh4/
path: /zapchasti/{brand}/{model}
filter: field_models CONTAINS {model_tid}
display: grid, 20 items/page

# /volvo/fh4/optics/
path: /zapchasti/{brand}/{model}/{node}
filter:
  - field_models CONTAINS {model_tid}
  - field_node = {node_tid}

# /optics/ (все марки)
path: /zapchasti/{node}
filter: field_node = {node_tid}

Преимущества:
- ✅ Нет дублей контента (один товар = одна страница)
- ✅ Множественные пути навигации
- ✅ Простая фильтрация (Facets)


Вариант В: Гибрид (лучшее решение)

Основная навигация:

Главная → Volvo → FH4 → [список всех деталей]
                         ↓
                    Фильтры (боковая панель):
                      - Кабина и кузов (12)
                        ├─ Оптика (4)
                        ├─ Зеркала (2)
                      - Шасси (8)
                        ├─ Подвеска (3)

URL:

/zapchasti/volvo/fh4/                     Все детали FH4
/zapchasti/volvo/fh4/?node=optics         Фильтр Оптика
/zapchasti/volvo/fh4/?aggregate=cabin     Фильтр Кабина

Facets навигация:

┌────────────────────────────────────────────────────────────┐
│ Запчасти Volvo FH4 2012                                    │
├────────────────────────────────────────────────────────────┤
│                                                             │
│ ┌───────────────┐  ┌──────────────────────────────────┐   │
│ │ ФИЛЬТРЫ       │  │ ТОВАРЫ (42)                      │   │
│ ├───────────────┤  ├──────────────────────────────────┤   │
│ │               │  │                                   │   │
│ │ Агрегат:      │  │ ┌─────────────────────────────┐  │   │
│ │ ☑ Кабина (12) │  │ │ Фара правая OEM 21467241    │  │   │
│ │ ☑ Шасси (8)   │  │ │ 8 500 ₽ - 12 000 ₽         │  │   │
│ │ ☐ Силовой (15)│  │ │ 3 варианта                  │  │   │
│ │ ☐ Электрика(7)│  │ └─────────────────────────────┘  │   │
│ │               │  │                                   │   │
│ │ Узел:         │  │ ┌─────────────────────────────┐  │   │
│ │ ☑ Оптика (4)  │  │ │ Амортизатор OEM 20499340    │  │   │
│ │ ☐ Зеркала (2) │  │ │ 5 200 ₽                     │  │   │
│ │ ☐ Подвеска(3) │  │ │ 2 варианта                  │  │   │
│ │               │  │ └─────────────────────────────┘  │   │
│ │ Состояние:    │  │                                   │   │
│ │ ☑ Б/У (35)    │  │ ...                              │   │
│ │ ☐ Новое (7)   │  │                                   │   │
│ └───────────────┘  └──────────────────────────────────┘   │
└────────────────────────────────────────────────────────────┘

КАК ИЩУТ ЛЮДИ

Поисковые запросы и их обработка:

"запчасти volvo фш4":
  → Search: title CONTAINS "volvo"
  → Suggestions: "Volvo FH4", "Volvo FM4"
  → Redirect: /zapchasti/volvo/fh4/

"фара вольво 4 фш":
  → Parse: "фара" + "вольво" + "4" + "фш"
  → Match: "Volvo FH4" (транслит: ФШ4)
  → Search: node=optics AND model=FH4

"volvo fh 2012":
  → Parse: brand=Volvo, model=FH, year=2012
  → Match: Volvo FH4 (year_start: 2012)
  → Redirect: /zapchasti/volvo/fh4/

"21467241":
  → Direct OEM search
  → Result: Фара правая OEM 21467241

"амортизатор вольво евро 6":
  → Parse: "амортизатор" + brand=Volvo + "euro6"
  → Filter: node=suspension AND platform=euro6
  → Results: FH4, FM4 амортизаторы

Search API конфигурация:

Index: products

Fields:
  - field_oem (boost: 10)
  - title (boost: 5)
  - field_cross (boost: 8)
  - field_models.name (boost: 6)
  - field_models.field_transliteration (boost: 7)
  - field_node.name (boost: 3)

Synonyms:
  - "вольво" → "volvo"
  - "фш" → "fh"
  - "фм" → "fm"
  - "сканиа" → "scania"

ИТОГОВОЕ РЕШЕНИЕ

1. База NocoDB — источник истины

CREATE TABLE models (
    id INT,
    name VARCHAR(100),        -- "Volvo FH4"
    old_code VARCHAR(20),     -- "4-FH" (для импорта)
    generation_id INT,
    platform VARCHAR(50),     -- "euro6"
    engines JSON,             -- ["D13K", "D13C"]
    year_start INT,           -- 2012
    transliteration VARCHAR   -- "ФШ4"
);

2. Импорт справочников (один раз)

drush scr import-nocodb-models.php
# → Создаёт taxonomy terms в Drupal

3. Импорт товаров (ежедневно)

// ЭТАП 1: Структуризатор
foreach ($csv as $row) {
    $models = parseCompatibility($row);  // "4-FH" + "4-FM" → [FH4, FM4]
    $node = determineNode($row['name']);  // "Фара" → Оптика
    $compat_type = $node->aggregate->compat_type;  // "model"

    $models = expandCompatibility($models, $compat_type);  // расширение

    createProduct($row['OEM'], $models, $node);
}

// ЭТАП 2: Вариации (SKU)
foreach ($csv as $row) {
    createVariation($row['OEM'], $row['SKU'], $row['price'], ...);
}

4. Организация каталога

URL: /zapchasti/{brand}/{model}/

Facets:
  - Агрегат (Кабина, Шасси...)
  - Узел (Оптика, Подвеска...)
  - Состояние (Б/У, Новое)
  - Цена (range)

Product page:
  URL: /zapchasti/{part-name}-{oem}/
  Canonical: на себя
  Совместимость: показать все модели из field_models

СЛЕДУЮЩИЕ ШАГИ

  1. ✅ Структура спроектирована
  2. ⏳ Заполнить NocoDB моделями (brands → generations → models)
  3. ⏳ Создать скрипт импорта справочников (NocoDB → Drupal)
  4. ⏳ Написать функцию expandCompatibility()
  5. ⏳ Создать импортёр товаров (BAZON CSV → Drupal Products)
  6. ⏳ Настроить Views для категорий
  7. ⏳ Настроить Search API + синонимы
  8. ⏳ Настроить Facets

Версия: 1.0.0
Дата: 2026-02-12