לקוח מתלונן: "בכרטיס המוצר כתוב '10 במלאי', אבל במציאות, בקניון המרכזי יש 5, ברחוב לנין יש 2, ובזאפאדני אין בכלל." רכיב ה-bitrix:catalog.element הסטנדרטי מסכם את המלאי בכל המחסנים ללא הפרדה בין נקודות איסוף. הלקוח מגיע לחנות ריקה — המוניטין נפגע.
אנחנו פותרים את הבעיה הזו: אנו מציגים את הכמות האמיתית לכל חנות ומעדכנים את הנתונים בעת בחירת הצעת סחר ללא טעינה מחדש. באמצעות שאילתה ישירה לטבלת מלאי המחסנים עם פילטר לפי נקודות איסוף ושימוש ב-caching מתויג, העמוד נטען במהירות והנתונים תמיד עדכניים. אינטגרציה עם 1C דרך CommerceML מבטיחה עדכון מלאי אוטומטי בכל העלאה. זמן טעינת העמוד יורד מ-200ms ל-50ms בזכות caching מתויג, ודיוק הנתונים מובטח באמצעות סנכרון עם 1C.
למה הרכיב הסטנדרטי לא מתאים
bitrix:catalog.element מציג את סך המלאי משדה CATALOG_QUANTITY. כדי לפרק לפי מחסן, צריך לבצע שאילתה משלך לטבלת b_catalog_store_product. הנה מבנה שאילתה טיפוסי:
SELECT s.ID, s.TITLE, s.ADDRESS, COALESCE(sp.AMOUNT, 0) as AMOUNT, COALESCE(sp.QUANTITY_RESERVED, 0) as RESERVED, COALESCE(sp.AMOUNT, 0) - COALESCE(sp.QUANTITY_RESERVED, 0) as AVAILABLE FROM b_catalog_store s LEFT JOIN b_catalog_store_product sp ON sp.STORE_ID = s.ID AND sp.PRODUCT_ID = ? WHERE s.ACTIVE = 'Y' AND s.IS_SITE = 'Y' ORDER BY s.SORT ASC; הדגל SELECT s.ID, s.TITLE, s.ADDRESS, COALESCE(sp.AMOUNT, 0) as AMOUNT, COALESCE(sp.QUANTITY_RESERVED, 0) as RESERVED, COALESCE(sp.AMOUNT, 0) - COALESCE(sp.QUANTITY_RESERVED, 0) as AVAILABLE FROM b_catalog_store s LEFT JOIN b_catalog_store_product sp ON sp.STORE_ID = s.ID AND sp.PRODUCT_ID = ? WHERE s.ACTIVE = 'Y' AND s.IS_SITE = 'Y' ORDER BY s.SORT ASC; מבדיל בין נקודות איסוף למחסנים רגילים. הוא מוגדר בלוח הניהול: קטלוג → מחסנים → [עריכה].
איך להבדיל בין נקודת איסוף למחסן בביטריקס
בלוח הניהול, לכל מחסן יש שדה "נקודת איסוף" (IS_SITE). אם הדגל מופעל, במהלך החלפה עם 1C, המלאי למחסן זה ייספר בנפרד. בפועל, זה מאפשר ניהול תצוגה גמיש: לדוגמה, חנות מקוונת יכולה להציג רק חנויות איסוף, תוך אי-הכללת מחסנים סיטונאיים.
איך אנחנו עושים את זה
אנחנו מפתחים את הפונקציה IS_SITE = 'Y', שמביאה מלאי לכל נקודות האיסוף הפעילות בשאילתה אחת. אנו משתמשים ב-Bitrix ORM: getStoreAvailability() ו-\Bitrix\Catalog\StoreTable. קוד PHP:
function getStoreAvailability(int $productId): array { $result = []; $storesQuery = \Bitrix\Catalog\StoreTable::getList([ 'filter' => ['ACTIVE' => 'Y', 'IS_SITE' => 'Y'], 'select' => ['ID', 'TITLE', 'ADDRESS', 'GPS_N', 'GPS_S', 'SORT'], 'order' => ['SORT' => 'ASC'], ]); $stores = []; while ($store = $storesQuery->fetch()) { $stores[$store['ID']] = $store; } if (empty($stores)) { return []; } // Остатки одним запросом для всех складов $stockQuery = \Bitrix\Catalog\StoreProductTable::getList([ 'filter' => [ 'PRODUCT_ID' => $productId, 'STORE_ID' => array_keys($stores), ], 'select' => ['STORE_ID', 'AMOUNT', 'QUANTITY_RESERVED'], ]); $stocks = []; while ($stock = $stockQuery->fetch()) { $stocks[$stock['STORE_ID']] = $stock; } foreach ($stores as $storeId => $store) { $amount = (float)($stocks[$storeId]['AMOUNT'] ?? 0); $reserved = (float)($stocks[$storeId]['QUANTITY_RESERVED'] ?? 0); $available = max(0, $amount - $reserved); $result[] = [ 'ID' => $storeId, 'TITLE' => $store['TITLE'], 'ADDRESS' => $store['ADDRESS'], 'GPS_N' => $store['GPS_N'], 'GPS_S' => $store['GPS_S'], 'AMOUNT' => $amount, 'AVAILABLE' => $available, 'IN_STOCK' => $available > 0, ]; } return $result; } הפתרון שלנו משתמש ב-caching מתויג (tag \Bitrix\Catalog\StoreProductTable) — כשהמלאי משתנה, ה-cache מתבטל אוטומטית. זה מהיר פי 5 משאילתה סטנדרטית ללא cache. עבור 50 מחסנים, השאילתה מתבצעת בפחות מ-10 אלפיות השנייה.
מה אם יש יותר מ-50 חנויות?
למספר גדול של נקודות איסוף (50+), אנחנו מייעלים שאילתות. אנו מוסיפים אינדקסים על שדות function getStoreAvailability(int $productId): array { $result = []; $storesQuery = \Bitrix\Catalog\StoreTable::getList([ 'filter' => ['ACTIVE' => 'Y', 'IS_SITE' => 'Y'], 'select' => ['ID', 'TITLE', 'ADDRESS', 'GPS_N', 'GPS_S', 'SORT'], 'order' => ['SORT' => 'ASC'], ]); $stores = []; while ($store = $storesQuery->fetch()) { $stores[$store['ID']] = $store; } if (empty($stores)) { return []; } // Остатки одним запросом для всех складов $stockQuery = \Bitrix\Catalog\StoreProductTable::getList([ 'filter' => [ 'PRODUCT_ID' => $productId, 'STORE_ID' => array_keys($stores), ], 'select' => ['STORE_ID', 'AMOUNT', 'QUANTITY_RESERVED'], ]); $stocks = []; while ($stock = $stockQuery->fetch()) { $stocks[$stock['STORE_ID']] = $stock; } foreach ($stores as $storeId => $store) { $amount = (float)($stocks[$storeId]['AMOUNT'] ?? 0); $reserved = (float)($stocks[$storeId]['QUANTITY_RESERVED'] ?? 0); $available = max(0, $amount - $reserved); $result[] = [ 'ID' => $storeId, 'TITLE' => $store['TITLE'], 'ADDRESS' => $store['ADDRESS'], 'GPS_N' => $store['GPS_N'], 'GPS_S' => $store['GPS_S'], 'AMOUNT' => $amount, 'AVAILABLE' => $available, 'IN_STOCK' => $available > 0, ]; } return $result; } ו-catalog_store_product בטבלת STORE_ID ומשתמשים ב-caching עם זמן חיים של 5 דקות. זה מאפשר טיפול בעד 1000 מחסנים ללא פגיעה בביצועים. אם יש צורך ברעננות מיידית, אפשר לבטל את ה-caching — אבל עומס מסד הנתונים גדל. אנו ממליצים על פשרה: caching מתויג עם ביטול בעת החלפת 1C.
אינטגרציה לתבנית כרטיס המוצר
ב-PRODUCT_ID של רכיב b_catalog_store_product:
\Bitrix\Main\Loader::includeModule('catalog'); $storeAvailability = getStoreAvailability($arResult['ID']); $inStockCount = count(array_filter($storeAvailability, fn($s) => $s['IN_STOCK'])); ?> <div class="store-availability"> <?php if ($inStockCount > 0): ?> <div class="in-stock-summary"> В наличии в <?= $inStockCount ?> магазин<?= \Local\Helpers\Declension::get($inStockCount, ['е', 'е', 'ах']) ?> </div> <button class="toggle-stores" type="button">Показать все магазины</button> <ul class="store-list" style="display:none"> <?php foreach ($storeAvailability as $store): ?> <li class="store-item <?= $store['IN_STOCK'] ? 'in-stock' : 'out-of-stock' ?>"> <span class="store-name"><?= htmlspecialchars($store['TITLE']) ?></span> <span class="store-address"><?= htmlspecialchars($store['ADDRESS']) ?></span> <span class="store-qty"> <?= $store['IN_STOCK'] ? $store['AVAILABLE'] . ' шт.' : 'Нет в наличии' ?> </span> </li> <?php endforeach; ?> </ul> <?php else: ?> <div class="out-of-stock">Нет в наличии в магазинах</div> <?php endif; ?> </div> עדכון AJAX בעת בחירת הצעת סחר
למוצרים עם וריאציות (מידה, צבע), אנו מעדכנים מלאי ללא טעינת עמוד מחדש:
document.querySelectorAll('.offer-option').forEach(function(el) { el.addEventListener('change', function() { var offerId = this.value; fetch('/ajax/store-availability/?product_id=' + offerId) .then(r => r.json()) .then(data => updateStoreList(data.stores)); }); }); ה-endpoint template.php הוא קובץ PHP נפרד שמחזיר JSON עם התוצאה של bitrix:catalog.element.
מה כלול בעבודה
- ניתוח סכמת המחסנים הנוכחית וסוגי ההצעות.
- פיתוח פונקציית שליפת המלאי עם caching.
- אינטגרציה של הבלוק לתבנית
\Bitrix\Main\Loader::includeModule('catalog'); $storeAvailability = getStoreAvailability($arResult['ID']); $inStockCount = count(array_filter($storeAvailability, fn($s) => $s['IN_STOCK'])); ?> <div class="store-availability"> <?php if ($inStockCount > 0): ?> <div class="in-stock-summary"> В наличии в <?= $inStockCount ?> магазин<?= \Local\Helpers\Declension::get($inStockCount, ['е', 'е', 'ах']) ?> </div> <button class="toggle-stores" type="button">Показать все магазины</button> <ul class="store-list" style="display:none"> <?php foreach ($storeAvailability as $store): ?> <li class="store-item <?= $store['IN_STOCK'] ? 'in-stock' : 'out-of-stock' ?>"> <span class="store-name"><?= htmlspecialchars($store['TITLE']) ?></span> <span class="store-address"><?= htmlspecialchars($store['ADDRESS']) ?></span> <span class="store-qty"> <?= $store['IN_STOCK'] ? $store['AVAILABLE'] . ' шт.' : 'Нет в наличии' ?> </span> </li> <?php endforeach; ?> </ul> <?php else: ?> <div class="out-of-stock">Нет в наличии в магазинах</div> <?php endif; ?> </div>. - יצירת נתיב AJAX להצעות סחר.
- בדיקות על נתונים אמיתיים (מ-2 עד 10 נקודות איסוף).
- תיעוד להתאמות עתידיות ומסירת גישה.
השוואה: פתרון סטנדרטי מול שלנו
| מאפיין | document.querySelectorAll('.offer-option').forEach(function(el) { el.addEventListener('change', function() { var offerId = this.value; fetch('/ajax/store-availability/?product_id=' + offerId) .then(r => r.json()) .then(data => updateStoreList(data.stores)); }); }); סטנדרטי |
ההתאמה שלנו |
|---|---|---|
| תצוגה לפי חנות | לא | כן, עם כתובת ו-GPS |
| Caching | לא | מתויג, ביטול אוטומטי |
| עדכון בעת שינוי הצעה | רק לאחר טעינה מחדש | AJAX ללא טעינה מחדש |
| זמן טעינת עמוד | ~200 אלפיות השנייה (ללא cache) | ~50 אלפיות השנייה (עם cache) |
בניגוד לפתרון הסטנדרטי, ההתאמה שלנו לא רק מציגה מלאי לפי חנות אלא גם מאפשרת ללקוח לראות את הכתובת וקואורדינטות GPS, נוחות לתכנון מסלול.
שלבים ולוח זמנים
| שלב | משימות | זמן |
|---|---|---|
| ניתוח | בדיקת מבנה המחסנים, סוגי הצעות, תבנית נוכחית | 1-2 שעות |
| פיתוח | פונקציית getStoreAvailability, caching, endpoint AJAX | 3-4 שעות |
| אינטגרציה | סמני בלוק בתבנית catalog.element, בדיקות | 2-3 שעות |
| בדיקות | בדיקה על נתונים אמיתיים, תרחישים עם מלאי | 1-2 שעות |
| תיעוד | תיאור התאמות, מסירת גישה | 0.5-1 שעה |
סה"כ: 8 עד 12 שעות בהתאם למורכבות הקטלוג ומספר המחסנים. צרו קשר — נעריך את הפרויקט שלכם תוך יום עסקים אחד.
מומחי 1C-Bitrix מוסמכים עם ניסיון של למעלה מ-5 שנים מבטיחים אינטגרציה ללא שגיאות ותמיכה מלאה. פנו אלינו לקבלת הערכת עלות. הזמינו את ההתקנה, והחנות המקוונת שלכם תציג מלאי אמיתי לכל נקודת איסוף.







