Server IP : 80.87.202.40 / Your IP : 216.73.216.169 Web Server : Apache System : Linux rospirotorg.ru 5.14.0-539.el9.x86_64 #1 SMP PREEMPT_DYNAMIC Thu Dec 5 22:26:13 UTC 2024 x86_64 User : bitrix ( 600) PHP Version : 8.2.27 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /home/bitrix/ext_www/rospirotorg.ru/bitrix/modules/wbs24.exchange1c/lib/ |
Upload File : |
<?php namespace Wbs24\Exchange1c; use Bitrix\Main\SystemException; class Catalog implements Interfaces\Catalog { use Exception; protected $wrappers; protected $Settings; protected $Formula; protected $Db; protected $warehouses; protected $moduleSettings; protected $allowedProductIds; protected $settingsSuffixes = [ '', //'ForOffers', ]; protected $settingsToProductFields = [ 'articlePropertyCode' => 'sku', //'colorPropertyCode' => 'color', //'sizePropertyCode' => 'size', 'barcodesProperty' => 'barcode', //'ratioPropertyCode' => 'packageRatio', ]; protected $requiredSettings = [ 'articlePropertyCode', ]; public function __construct($objects = []) { $this->wrappers = new Wrappers($objects); $this->Settings = $objects['Settings'] ?? new Settings($objects); $this->Formula = $objects['Formula'] ?? new Formula($objects); $this->Db = $objects['Db'] ?? new Db($objects); $this->moduleSettings = $this->Settings->get(); } public function setProducts(array $products): array { $report = [ 'updated_stocks_for_product_ids' => [], 'updated_prices_for_product_ids' => [], 'updated_skus' => [], 'not_found' => [], ]; $settings = $this->moduleSettings; $updateOnlyAllowedProducts = $settings['updateOnlyAllowedProducts'] ?? 'Y'; $allowedProductIds = ($updateOnlyAllowedProducts == 'Y') ? $this->getAllowedProductIds() : []; foreach ($products as $product) { $productId = false; //$barcodes = $this->getBarcodes($product); // отключено $sku = $product['sku'] ?? false; // поиск по barcodes - отключено /* if ($settings['searchByBarcode'] == 'Y') { $productId = $this->getProductIdByBarcode($barcodes); } */ // поиск по sku if (!$productId && $settings['searchBySku'] == 'Y') { $productId = $this->getProductId($product); //$this->setBarcodes($productId, $barcodes); // отключено } if (!$productId || ( $updateOnlyAllowedProducts == 'Y' && !in_array($productId, $allowedProductIds) ) ) { $report['not_found'][] = $product; continue; } // конвертация в комплекты - отключена // $productMeta = $this->getProductMeta($productId); // $packageRatio = $this->getPackageRatio($productMeta); // задан коэф упаковки по умолчанию $packageRatio = 1; // обновить остатки на складах $stocks = $this->getProductStocks($product, $packageRatio); $updateStocksResult = $this->updateStocks($productId, $stocks); // обновить доступное кол-во $updateAvailableQuantityResult = $this->updateAvailableQuantity($productId, $product, $packageRatio); // обновление цен - отключено //$prices = $this->getProductPrices($product, $packageRatio); //$updatePricesResult = $this->updatePrices($productId, $prices); if ($updateStocksResult || $updateAvailableQuantityResult) { $report['updated_stocks_for_product_ids'][] = $productId; $report['updated_skus'][] = $sku; } if ($updatePricesResult) { $report['updated_prices_for_product_ids'][] = $productId; $report['updated_skus'][] = $sku; } } $report['updated_skus'] = array_unique($report['updated_skus']); if ($this->moduleSettings['debug']) { $this->createReport('update.log', $report); } return $report; } public function getAllowedProductIds() { if ($this->allowedProductIds !== null) return $this->allowedProductIds; $productIds = []; $settings = $this->moduleSettings; // поиск товаров $products = $this->getProducts([ 'filter' => [ 'PROPERTY_'.$settings['allowTransferProperty'] => 'Да', ], ]); foreach ($products as $product) { $productId = $product['ID'] ?? false; if ($productId) $productIds[] = $productId; } // поиск ТП /* if ($productIds) { $offers = $this->getProducts([ 'filter' => [ 'PROPERTY_'.$settings['linkProductPropertyForOffers'] => $productIds, ], ]); foreach ($offers as $product) { $productId = $product['ID'] ?? false; if ($productId && !in_array($productId, $productIds)) $productIds[] = $productId; } } */ $this->allowedProductIds = $productIds; return $productIds; } public function dropStocksExcludeProductsIds(array $excludeProductsIds) { if ($this->moduleSettings['allowDropStocksIfNotTransfer'] != 'Y') return; $stocks = $this->Db->get('b_catalog_store_product'); $allowedProductIds = $this->getAllowedProductIds(); $warehouses = $this->getWarehouses(); foreach ($stocks as $stockInfo) { $curProductId = $stockInfo['PRODUCT_ID'] ?? false; $storeId = $stockInfo['STORE_ID'] ?? false; if ( $curProductId && $storeId !== false && !in_array($curProductId, $excludeProductsIds) && in_array($curProductId, $allowedProductIds) ) { // если склад не доступен, пропустить if (!isset($warehouses[$storeId])) continue; // сброс остатков на складе $this->Db->clear('b_catalog_store_product', [ 'PRODUCT_ID' => $curProductId, 'STORE_ID' => $storeId, ]); // сброс доступного кол-ва $this->setAvailableQuantity($curProductId, 0); } } } public function getProductId(array $product): ?int { $productId = null; $filter = $this->getFilter($product); if ($filter) { $products = $this->getProducts([ 'filter' => $filter, 'limit' => 2, ]); if (count($products) > 1) { $productId = null; } else { $productId = $products[0]['ID'] ?? null; } } return $productId; } public function getProductMeta(int $productId): array { $productMeta = []; $settingsSuffixes = $this->settingsSuffixes; $settingsToProductFields = $this->settingsToProductFields; $settings = $this->moduleSettings; $select = ['ID', 'IBLOCK_ID']; foreach ($settingsSuffixes as $suffix) { foreach ($settingsToProductFields as $settingsKey => $productField) { $propCode = $settings[$settingsKey.$suffix]; if (!$propCode) continue; $preparedPropCode = $this->getPropertyCode($propCode); $select[] = 'PROPERTY_'.$preparedPropCode; } } $products = $this->getProducts([ 'filter' => [ 'ID' => $productId, ], 'select' => $select, 'limit' => 1, ]); $product = $products[0] ?? false; if ($product) { foreach ($settingsSuffixes as $suffix) { foreach ($settingsToProductFields as $settingsKey => $productField) { $propCode = $settings[$settingsKey.$suffix]; if (!$propCode) continue; $preparedPropCode = $this->getPropertyCodeWithValueSuffix($propCode); $code = 'PROPERTY_'.$preparedPropCode; $value = $product[$code] ?? ''; if (is_array($value)) $value = $value[0] ?? ''; if (empty($productMeta[$productField])) $productMeta[$productField] = $value; } } } // если packageRatio не найден в элементе каталога /* if (!$productMeta['packageRatio']) { $productMeta['packageRatio'] = $this->getPackageRatio($productMeta); } */ return $productMeta; } protected function getBarcodes(array $product): array { $barcodes = $product['barcode'] ?: []; if (!is_array($barcodes)) $barcodes = [$barcodes]; return $barcodes; } protected function getProductIdByBarcode(array $barcodes): ?int { $settings = $this->moduleSettings; $propertyCode = $settings['barcodesProperty'] ?: false; if (!$propertyCode || !$barcodes) return null; $productId = null; foreach ($barcodes as $barcode) { $products = $this->getProducts([ 'filter' => [ 'PROPERTY_'.$propertyCode => $barcode, ], 'select' => ['ID', 'IBLOCK_ID'], 'limit' => 1, ]); $productId = $products[0]['ID'] ?? null; if ($productId) break; } return $productId; } protected function setBarcodes(?int $productId, array $barcodes) { if (!$productId || !$barcodes) return; $settings = $this->moduleSettings; $propertyCode = $settings['barcodesProperty'] ?: false; if ($propertyCode) $this->wrappers->CIBlockElement->SetPropertyValuesEx($productId, null, [ $propertyCode => $barcodes, ]); } // не используется protected function getPackageRatio(array $productMeta): int { $sku = $productMeta['sku'] ?? ''; $ratio = $this->getPackageRatioFromSku($sku); return $ratio; } // не используется protected function getPackageRatioFromSku(string $sku): int { $ratio = 1; if ($sku) { preg_match("/(.+)_(.+)_(.+)_(.+)_(.+)/", $sku, $matches); $foundRatio = $matches[4] ?: false; if ($foundRatio) { $ratio = intval($foundRatio); if ($ratio < 1) $ratio = 1; } } return $ratio; } protected function getPropertyCode(string $code): string { if (substr($code, -6) == '_VALUE') $code = substr($code, 0, -6); return $code; } protected function getPropertyCodeWithValueSuffix(string $code): string { if ($code && substr($code, -6) != '_VALUE') $code .= '_VALUE'; return $code; } protected function getProducts(array $param): array { $sort = $param['sort'] ?? []; $filter = $param['filter'] ?? []; $select = $param['select'] ?? ['ID', 'IBLOCK_ID']; $limit = $param['limit'] ?? false; $products = []; $i = 0; $result = $this->wrappers->CIBlockElement->GetList( $sort, $filter, false, false, $select ); while ($fields = $result->Fetch()) { $i++; if ($limit && $i > $limit) break; $products[] = $fields; } return $products; } // не используется - нужен для одновременного поиска по простым товарам и ТП protected function getFilterWithOrLogic(array $product): array { $fullFilter = ['LOGIC' => 'OR']; $settingsSuffixes = $this->settingsSuffixes; foreach ($settingsSuffixes as $suffix) { $fullFilter[] = $this->getFilter($product, $suffix); } return $fullFilter; } protected function getFilter(array $product, string $settingSuffix = ''): array { $settingsToProductFields = $this->settingsToProductFields; $requiredSettings = $this->requiredSettings; $settings = $this->moduleSettings; $error = false; foreach ($settingsToProductFields as $settingsKey => $productField) { $continue = false; $propertyCode = $settings[$settingsKey.$settingSuffix] ?? false; if (!$propertyCode) $continue = true; $productFieldValue = $product[$productField] ?? false; if (!$productFieldValue) $continue = true; if ($continue) { if (in_array($settingsKey, $requiredSettings)) { $error = true; break; } continue; } // игнорировать значения MIX (то есть в ассортименте) //if ($settingsKey.$settingSuffix == 'articlePropertyCodeForOffers') $productFieldValue .= '_%'; //if ($productFieldValue != 'MIX') $filter['PROPERTY_'.$propertyCode] = $productFieldValue; $filter['PROPERTY_'.$propertyCode] = $productFieldValue; } if ($error) $filter = []; return $filter; } protected function getProductStocks(array $product, int $packageRatio = 1): array { $stocks = []; $warehousesInfo = $this->getWarehousesInfo(); foreach ($warehousesInfo as $warehouseId => $info) { $stocks[$warehouseId] = 0; } $productStocks = $product['stocks'] ?? []; foreach ($productStocks as $info) { $warehouseId = $this->getWarehouseIdFromStockInfo($info, $warehousesInfo); if (!$warehouseId) continue; if (!isset($stocks[$warehouseId])) continue; $singleStock = intval($info['quantity'] ?? 0); $stock = floor($singleStock / $packageRatio); // с учетом упаковки $stocks[$warehouseId] = $stock; } return $stocks; } protected function getWarehouseIdFromStockInfo(array $stockInfo, array $warehousesInfo) { $warehouseId = $stockInfo['stockId'] ?? false; if ($warehouseId && !is_numeric($warehouseId)) { foreach ($warehousesInfo as $id => $info) { if ($warehouseId == $info['XML_ID']) { $warehouseId = $id; break; } } } return $warehouseId; } public function getWarehouses(): array { if (!$this->warehouses) { $warehouses = []; $warehousesInfo = $this->getWarehousesInfo(); foreach ($warehousesInfo as $id => $info) { $warehouses[$id] = $info['TITLE']; } $this->warehouses = $warehouses; } return $this->warehouses; } public function getWarehousesInfo(): array { $warehousesInfo = []; $settings = $this->moduleSettings; $filter = ['ACTIVE' => 'Y']; $allowedWarehouseIdsAsString = $settings['allowedWarehouseIds'] ?? ''; $allowedWarehouseIds = explode(',', $allowedWarehouseIdsAsString); $filter['ID'] = $allowedWarehouseIds; if ($allowedWarehouseIdsAsString) { $result = $this->wrappers->StoreTable->getList([ 'filter' => $filter, 'select' => ['ID', 'XML_ID', 'TITLE'], ]); while ($fields = $result->Fetch()) { $warehousesInfo[$fields['ID']] = $fields; } } return $warehousesInfo; } protected function updateStocks(int $productId, array $stocks): bool { $warehouseStocks = $this->getStocksFromWarehouses($productId); $newWarehouseStocks = []; $needUpdate = false; foreach ($stocks as $warehouseId => $quantity) { $currentWarehouseInfo = $warehouseStocks[$warehouseId] ?? [ 'ID' => false, 'AMOUNT' => 0, 'STORE_ID' => $warehouseId, ]; $currentWarehouseInfo['AMOUNT'] = $quantity; $newWarehouseStocks[$warehouseId] = $currentWarehouseInfo; } $success = $this->updateStocksInWarehouses($productId, $newWarehouseStocks); return $success; } /** * Если переданы остатки для доступного кол-ва - применить их */ protected function updateAvailableQuantity(int $productId, array $product, int $packageRatio = 1) { $success = false; $availableQuantity = $product['availableQuantity'] ?? false; if ($availableQuantity !== false) { $success = true; $quantity = $availableQuantity / $packageRatio; $this->setAvailableQuantity($productId, $quantity); } return $success; } public function setAvailableQuantity(int $productId, int $quantity) { $fields = ['QUANTITY' => $quantity]; $this->wrappers->CCatalogProduct->Update($productId, $fields); } protected function getStocksFromWarehouses(int $productId): array { $warehouseStocks = []; $result = $this->wrappers->StoreProductTable->getList([ 'filter' => ['=PRODUCT_ID' => $productId, 'STORE.ACTIVE' => 'Y'], 'select' => ['ID', 'AMOUNT', 'STORE_ID'], ]); while ($fields = $result->Fetch()) { $warehouseId = $fields['STORE_ID']; $warehouseStocks[$warehouseId] = $fields; } return $warehouseStocks; } protected function updateStocksInWarehouses(int $productId, array $newStocks): bool { $success = false; $countSuccess = 0; $results = []; foreach ($newStocks as $warehouseId => $info) { $id = $info['ID'] ?: false; unset($info['ID']); if ($id) { $results[] = $this->wrappers->StoreProductTable->update( $id, $info ); } else { $info['PRODUCT_ID'] = $productId; $results[] = $this->wrappers->StoreProductTable->add($info); } } foreach ($results as $result) { if ($result->isSuccess()) $countSuccess++; } if ($countSuccess == count($results)) $success = true; return $success; } protected function getProductPrices(array $product, int $packageRatio = 1): array { $prices = []; $settings = $this->moduleSettings; $singleWholesalePrice = $product['wholesalePrice'] ?? 0; $wholesalePrice = $singleWholesalePrice * $packageRatio; // с учетом упаковки $this->Formula->setMarks(['WHOLESALE_PRICE']); $this->Formula->setFormula($settings['basePriceFormula']); $basePrice = $this->Formula->calc([ 'WHOLESALE_PRICE' => $wholesalePrice, ]); if ($settings['updateBasePrice'] == 'Y') { $prices[$settings['basePriceType']] = $basePrice; } if ($settings['updateWholesalePrice'] == 'Y') { $prices[$settings['wholesalePriceType']] = $wholesalePrice; } return $prices; } protected function updatePrices(int $productId, array $prices): bool { $success = false; $countSuccess = 0; foreach ($prices as $priceType => $price) { $successStep = $this->updatePrice($productId, $priceType, $price); if ($successStep) $countSuccess++; } if ($countSuccess == count($prices)) $success = true; return $success; } protected function updatePrice(int $productId, int $priceType, float $price): bool { $success = false; $settings = $this->moduleSettings; $loadFields = [ "PRODUCT_ID" => $productId, "CATALOG_GROUP_ID" => $priceType, "PRICE" => $price, "CURRENCY" => $settings['currency'], ]; $priceResult = $this->wrappers->Price->getList([ "filter" => [ "PRODUCT_ID" => $productId, "CATALOG_GROUP_ID" => $priceType, ], ]); if ($priceFields = $priceResult->fetch()) { $updateResult = $this->wrappers->Price->update($priceFields["ID"], $loadFields); } else { $updateResult = $this->wrappers->Price->add($loadFields); } if ($updateResult->isSuccess()) $success = true; return $success; } }