> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aftersell.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Группировка строк бандла из другого приложения

> Используйте setInternalProperties, чтобы сообщить корзине Aftersell, какие строки принадлежат одному бандлу, чтобы они отображались как один товар, а не как несколько несвязанных строк.

Большинство приложений для бандлов формируют бандл, добавляя **каждый компонент отдельной строкой корзины**, а затем связывая их через собственные line item properties. Ajax API Shopify передаёт эти строки корзине без каких-либо указаний на их связь, поэтому по умолчанию панель показывает бандл из трёх частей как три несвязанных товара, каждый со своей ценой и переключателем количества.

`setInternalProperties` — это способ сообщить корзине, что они являются единым целым.

<div id="how-grouping-works">
  ## Как работает группировка
</div>

Корзина группирует строки по двум **каноническим свойствам**. Она не знает имена свойств вашего приложения для бандлов, поэтому вы выполняете перевод: читаете то, что записало приложение, и проставляете каноническую пару на каждую строку с помощью [line transform](/ru/aftersell/cart/sdk-hooks#registerlinetransform).

| Свойство                      | Обязательно | Значение                                                              |
| ----------------------------- | ----------- | --------------------------------------------------------------------- |
| `_aftersell_cart_bundle_id`   | Да          | Общий идентификатор. Все строки с одинаковым ID — один бандл.         |
| `_aftersell_cart_bundle_role` | Нет         | Установите `parent` на строке, под которой должен отображаться бандл. |

Они передаются через `setInternalProperties`, а не через Shopify. Это **оверлей только для отображения**: они никогда не попадают в `properties`, никогда не сохраняются в Shopify и никогда не появляются в заказе.

<div id="step-1-find-out-what-your-app-writes">
  ## Шаг 1: выясните, что записывает ваше приложение
</div>

Каждое приложение для бандлов называет свои свойства по-разному, поэтому начните с изучения реальной корзины. Добавьте бандл на вашей витрине, затем выполните это в консоли браузера:

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.actions.getCart().items.forEach((line) => {
  console.log(line.title, line.properties);
});
```

Вы ищете свойство, общее для всех строк бандла. Обычно это скрытое свойство (имя начинается с `_`), содержащее ID, ссылку или название бандла. Типичны варианты вроде `_bundle_id`, `_bundle_ref` или `_parent_id`. Запишите точный ключ и отметьте, помечена ли одна из строк как основной товар.

<div id="step-2-map-it-onto-the-canonical-properties">
  ## Шаг 2: сопоставьте это с каноническими свойствами
</div>

Вставьте в **Cart settings → Custom script → Initialization**, заменив имена свойств на найденные вами:

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.hooks.registerLineTransform((line) => {
  const props = line.properties;
  if (!props) return;

  const bundleId = props._bundle_id;
  if (!bundleId) return;

  line.setInternalProperties({
    _aftersell_cart_bundle_id: bundleId,
    // Mark the main product so the bundle renders under it.
    _aftersell_cart_bundle_role: props._bundle_role === 'main' ? 'parent' : 'child',
  });
});
```

Это вся интеграция. Как только две или более строк имеют общий ID, корзина сворачивает их в один бандл.

<Note>
  Если ваше приложение не помечает основной товар, полностью опустите `_aftersell_cart_bundle_role`. Корзина выберет якорь за вас.
</Note>

<div id="what-you-get">
  ## Что вы получаете
</div>

После группировки строк якорная строка несёт [объект `bundle`](/ru/aftersell/cart/sdk-cart-object#bundles), и панель отображает бандл как один товар:

* **Дочерние строки вкладываются под якорь**, а не отображаются отдельными рядами.
* **Количество атомарно.** Изменение количества бандла масштабирует всех участников вместе, используя коэффициент `perAnchorQty` каждого дочернего элемента, поэтому бандл с двумя единицами одного компонента сохраняет это соотношение два к одному.
* **Удаление атомарно.** Удаление бандла удаляет все строки участников одним запросом, а не оставляет осиротевшие компоненты.
* **Одна строка цены.** Что она показывает, зависит от настройки **bundle price** в блоке [Cart items](/ru/aftersell/cart/cart-items-block): сумму всех участников или только цену основного товара.

<div id="how-the-anchor-is-chosen">
  ## Как выбирается якорь
</div>

Якорь — это строка, под которой отображается бандл. Корзина выбирает его в следующем порядке:

1. Строка с `_aftersell_cart_bundle_role`, установленным в `parent`.
2. Иначе — участник **с самой высокой ценой**.
3. Иначе — первый участник в корзине.

Резервный вариант по цене обычно верен, поскольку приложения для бандлов, как правило, применяют скидку к основному товару. Устанавливайте роль явно, когда это не так, например когда основной товар — самый дешёвый или бесплатный.

<div id="rules-worth-knowing">
  ## Правила, которые стоит знать
</div>

* **Бандлу нужно минимум две строки.** Одиночная строка с ID бандла остаётся нетронутой и отображается обычным образом.
* **Нативные бандлы Shopify уже обрабатываются.** Строки, которые сам Shopify помечает как компонентные, пропускаются этой группировкой и адаптируются автоматически. Это нужно только для приложений, добавляющих отдельные строки.
* **Трансформация выполняется при каждом рендере.** Держите её лёгкой и без побочных эффектов. Не вызывайте действия и не выполняйте fetch внутри неё.
* **Слияние аддитивно.** Ваши свойства объединяются с установленными другой трансформацией. При реальном конфликте по одному и тому же ключу побеждает последняя зарегистрированная трансформация.
* **Группировка выполняется после скрытия и переименования** и до сортировки. Поэтому строка, скрытая через `setHidden`, никогда не станет частью бандла, а [компаратор](/ru/aftersell/cart/sdk-hooks#registerlinecomparator) видит якорь, а не дочерние строки.

<Warning>
  **Сгруппированные дочерние строки покидают `state.items`.** После сворачивания строк в бандл только якорь появляется в `getCart().items` и в payload событий; дочерние строки перемещаются в `anchor.bundle.children`. Они также перестают учитываться в `itemCount`.

  **Итоговая сумма корзины не затрагивается**, поскольку суммы приходят напрямую из Shopify. Группировка меняет представление, но никогда — то, что платит покупатель.
</Warning>

<div id="reading-a-bundle-back">
  ## Чтение бандла
</div>

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.events.on('cart_updated', (state) => {
  state.items.forEach((line) => {
    if (!line.bundle) return;
    console.log(line.title, 'is a bundle of', line.bundle.children.length, 'items:');
    line.bundle.children.forEach((child) => {
      console.log('  ', child.quantity, 'x', child.title);
    });
  });
});
```

Чтобы работать со строками бандла, используйте `bundle.memberKeys`, содержащий `key` каждого участника, включая якорь.

<div id="using-it-for-other-things">
  ## Использование для других задач
</div>

Группировка бандлов — то, для чего был создан `setInternalProperties`, но оверлей — это общий канал для **данных только для отображения, которые вы вычисляете из строки**. Всё, что вы туда помещаете, доступно для чтения в `line.internalProperties` и в блоке [Custom code](/ru/aftersell/cart/custom-code-blocks), не затрагивая реальную корзину:

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.hooks.registerLineTransform((line) => {
  if (line.properties?._preorder_ship_date) {
    line.setInternalProperties({ _badge: `Ships ${line.properties._preorder_ship_date}` });
  }
});
```

Используйте это, когда значение **вычисляемое** и предназначено только для отображения. Если данные должны сохраниться до заказа, им место в реальном line item property, устанавливаемом через скрытый input `properties[...]` на форме товара, чтобы значение доходило до Shopify независимо от того, кто выполняет добавление.

<div id="where-to-go-next">
  ## Что дальше
</div>

* **[`registerLineTransform`](/ru/aftersell/cart/sdk-hooks#registerlinetransform)**: хук, через который это выполняется.
* **[Объект корзины](/ru/aftersell/cart/sdk-cart-object#bundles)**: структура `bundle` и его дочерних элементов.
* **[Блок Cart items](/ru/aftersell/cart/cart-items-block)**: настройка цены бандла.
