> ## 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 بالأسطر التي تنتمي إلى نفس الحزمة، بحيث تُعرض كعنصر واحد بدلًا من عدة أسطر غير مترابطة.

معظم تطبيقات الحزم تبني الحزمة بإضافة **كل مكوّن كسطر مستقل في السلة**، ثم تربطها بخصائص عناصر أسطر من تصميمها الخاص. تسلّم Ajax API الخاصة بـ Shopify تلك الأسطر إلى السلة دون أي إشارة إلى أنها تنتمي إلى بعضها، لذا يعرض الدُرج افتراضيًا حزمة من ثلاثة أجزاء كثلاثة عناصر غير مترابطة، لكل منها سعره وأزرار كميته الخاصة.

`setInternalProperties` هي الطريقة التي تخبر بها السلة بأنها شيء واحد.

<div id="how-grouping-works">
  ## كيف يعمل التجميع
</div>

تجمّع السلة الأسطر بناءً على **خاصيتين قياسيتين**. فهي لا تعرف أسماء خصائص تطبيق الحزم لديك، لذا عليك الترجمة: اقرأ ما كتبه التطبيق، وادمغ الزوج القياسي على كل سطر باستخدام [محوّل سطر](/ar/aftersell/cart/sdk-hooks#registerlinetransform).

| الخاصية                       | مطلوبة | القيمة                                                         |
| ----------------------------- | ------ | -------------------------------------------------------------- |
| `_aftersell_cart_bundle_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);
});
```

أنت تبحث عن خاصية مشتركة بين أسطر الحزمة. عادةً ما تكون خاصية مخفية (يبدأ اسمها بـ `_`) تحمل معرّفًا أو مرجعًا أو اسم الحزمة. شيء مثل `_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',
  });
});
```

هذا هو التكامل بأكمله. بمجرد أن يتشارك سطران أو أكثر في معرّف واحد، تطوي السلة الأسطر في حزمة واحدة.

<Note>
  إذا كان تطبيقك لا يعلّم منتجًا رئيسيًا، فاترك `_aftersell_cart_bundle_role` بالكامل. ستختار السلة مرساة لك.
</Note>

<div id="what-you-get">
  ## ما الذي تحصل عليه
</div>

بمجرد تجميع الأسطر، يحمل سطر المرساة [كائن `bundle`](/ar/aftersell/cart/sdk-cart-object#bundles) ويعرض الدُرج الحزمة كعنصر واحد:

* **تتداخل العناصر التابعة تحت المرساة** بدلًا من الظهور كصفوف منفصلة.
* **الكمية ذرّية.** تغيير كمية الحزمة يوسّع كل عضو معًا، باستخدام نسبة `perAnchorQty` لكل عنصر تابع، لذا فإن حزمة تحتوي على اثنين من أحد المكونات تحافظ على علاقة الاثنين إلى واحد تلك.
* **الإزالة ذرّية.** إزالة الحزمة تزيل كل سطر عضو في طلب واحد، بدلًا من ترك مكونات يتيمة خلفها.
* **صف سعر واحد.** ما يعرضه يتبع إعداد **سعر الحزمة** في كتلة [Cart items](/ar/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>

* **تحتاج الحزمة إلى سطرين على الأقل.** السطر الواحد الذي يحمل معرّف حزمة يُترك وشأنه ويُعرض بشكل عادي.
* **حزم Shopify الأصلية معالجة بالفعل.** الأسطر التي تعلّمها Shopify نفسها كمكوَّنة يتخطاها هذا التجميع وتُكيَّف تلقائيًا. لا تحتاج إلى هذا إلا للتطبيقات التي تضيف أسطرًا منفصلة.
* **يعمل المحوّل عند كل عرض.** أبقِه خفيفًا وخاليًا من الآثار الجانبية. لا تستدعِ إجراءات أو تُجرِ استدعاء fetch من داخله.
* **الدمج تراكمي.** خصائصك تندمج مع أي خصائص عيّنها محوّل آخر. عند تعارض حقيقي على نفس المفتاح، يفوز المحوّل المسجَّل أخيرًا.
* **يعمل التجميع بعد الإخفاء وإعادة التسمية**، وقبل الفرز. لذا فإن السطر الذي تخفيه بـ `setHidden` لا يصبح أبدًا جزءًا من حزمة، و[المقارِن](/ar/aftersell/cart/sdk-hooks#registerlinecomparator) يرى المرساة وليس العناصر التابعة.

<Warning>
  **العناصر التابعة المجمّعة تغادر `state.items`.** بمجرد طي الأسطر في حزمة، تظهر المرساة فقط في `getCart().items` وفي حمولات الأحداث؛ وتنتقل العناصر التابعة إلى `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` وفي [كتلة التعليمات البرمجية المخصصة](/ar/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}` });
  }
});
```

استخدمها عندما تكون القيمة **مشتقة** ولأغراض العرض فقط. إذا كان يجب أن تصل البيانات إلى الطلب، فمكانها خاصية عنصر سطر حقيقية، تُعيَّن عبر حقل مخفي `properties[...]` في نموذج المنتج بحيث تصل إلى Shopify أيًا كان من يقوم بالإضافة.

<div id="where-to-go-next">
  ## إلى أين تذهب بعد ذلك
</div>

* **[`registerLineTransform`](/ar/aftersell/cart/sdk-hooks#registerlinetransform)**: الخُطاف الذي يمر هذا عبره.
* **[كائن السلة](/ar/aftersell/cart/sdk-cart-object#bundles)**: شكل `bundle` وعناصره التابعة.
* **[كتلة Cart items](/ar/aftersell/cart/cart-items-block)**: إعداد سعر الحزمة.
