1
0
mirror of synced 2024-12-12 07:06:04 +03:00

Динамическое добавление свойств в ICML (#375)

This commit is contained in:
Kocmonavtik 2024-12-10 14:33:00 +03:00 committed by GitHub
parent 1ec3bccd0c
commit 3162031b65
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 817 additions and 118 deletions

View File

@ -1,3 +1,6 @@
## 2024-12-09 v6.6.0
- Добавлено динамическое изменение свойств товаров при настройке экспорта
## 2024-12-08 v6.5.39 ## 2024-12-08 v6.5.39
- Исправлена поломка заказов с промокодом Maxma при включенной передаче корзины в CRM - Исправлена поломка заказов с промокодом Maxma при включенной передаче корзины в CRM

View File

@ -1 +1 @@
- Исправлена поломка заказов с промокодом Maxma при включенной передаче корзины в CRM - Добавлено динамическое изменение свойств товаров при настройке экспорта

View File

@ -38,7 +38,8 @@ if (file_exists($_SERVER['DOCUMENT_ROOT'] . '/bitrix/php_interface/retailcrm/exp
} }
} }
$settingService = SettingsService::getInstance([], ''); global $PROFILE_ID;
$settingService = SettingsService::getInstance([], '', $PROFILE_ID);
$iblockPropertySku = []; $iblockPropertySku = [];
$iblockPropertySkuHl = []; $iblockPropertySkuHl = [];
$iblockPropertyUnitSku = []; $iblockPropertyUnitSku = [];
@ -46,7 +47,7 @@ if (file_exists($_SERVER['DOCUMENT_ROOT'] . '/bitrix/php_interface/retailcrm/exp
$iblockPropertyProductHl = []; $iblockPropertyProductHl = [];
$iblockPropertyUnitProduct = []; $iblockPropertyUnitProduct = [];
foreach (array_keys($settingService->actrualPropList) as $prop) { foreach (array_keys($settingService->actualPropList) as $prop) {
$skuUnitProps = ('iblockPropertyUnitSku_' . $prop); $skuUnitProps = ('iblockPropertyUnitSku_' . $prop);
$skuUnitProps = $$skuUnitProps; $skuUnitProps = $$skuUnitProps;

View File

@ -26,7 +26,8 @@ CModule::IncludeModule('intaro.retailcrm');
//TODO заменить вызов на сервис-локатор, когда он приедет //TODO заменить вызов на сервис-локатор, когда он приедет
$settingsService = SettingsService::getInstance( $settingsService = SettingsService::getInstance(
$arOldSetupVars ?? [], $arOldSetupVars ?? [],
$ACTION $ACTION,
$PROFILE_ID
); );
$isSetupModulePage = $settingsService->isSetupModulePage(); $isSetupModulePage = $settingsService->isSetupModulePage();
@ -110,7 +111,7 @@ if ($STEP === 1) {
} }
</style> </style>
<form method="post" action="<?=$APPLICATION->GetCurPage()?>"> <form method="post" id="submit-form" action="<?=$APPLICATION->GetCurPage()?>">
<?php <?php
if ($ACTION === 'EXPORT_EDIT' || $ACTION === 'EXPORT_COPY') { if ($ACTION === 'EXPORT_EDIT' || $ACTION === 'EXPORT_COPY') {
?> ?>
@ -190,8 +191,7 @@ if ($STEP === 1) {
<tbody> <tbody>
<?php <?php
foreach ($settingsService->actrualPropList as $propertyKey => $property) { foreach ($settingsService->defaultPropList as $propertyKey => $property) { ?>
$productSelected = false; ?>
<tr class="adm-list-table-row"> <tr class="adm-list-table-row">
<td class="adm-list-table-cell"> <td class="adm-list-table-cell">
@ -215,14 +215,14 @@ if ($STEP === 1) {
if ($keyField === $propertyKey) { ?> if ($keyField === $propertyKey) { ?>
<option value="<?=$field['CODE']?>" <option value="<?=$field['CODE']?>"
<?php <?php
$productSelected = $settingsService->isOptionSelected( $isSelected = $settingsService->isOptionSelected(
$field, $field,
$arIBlock['OLD_PROPERTY_PRODUCT_SELECT'], $arIBlock['OLD_PROPERTY_PRODUCT_SELECT'],
$propertyKey $propertyKey
); );
?> ?>
<?= $productSelected ? ' selected' : ''?> <?= $isSelected ? ' selected' : ''?>
> >
<?=$field['name']?> <?=$field['name']?>
</option> </option>
@ -241,7 +241,7 @@ if ($STEP === 1) {
<?php <?php
echo $settingsService->getOptionClass($prop, true); echo $settingsService->getOptionClass($prop, true);
$productSelected = $settingsService->isOptionSelected( $isSelected = $settingsService->isOptionSelected(
$prop, $prop,
$arIBlock['OLD_PROPERTY_PRODUCT_SELECT'], $arIBlock['OLD_PROPERTY_PRODUCT_SELECT'],
$propertyKey $propertyKey
@ -251,7 +251,7 @@ if ($STEP === 1) {
= $settingsService->getHlTableName($prop) = $settingsService->getHlTableName($prop)
?? $productHlTableName; ?? $productHlTableName;
echo $productSelected ? ' selected' : ''; echo $isSelected ? ' selected' : '';
?> ?>
> >
<?=$prop['NAME']?> <?=$prop['NAME']?>
@ -376,7 +376,7 @@ if ($STEP === 1) {
<option value="<?=$prop['CODE']?>" <option value="<?=$prop['CODE']?>"
<?php <?php
echo $settingsService->getOptionClass($prop, false); echo $settingsService->getOptionClass($prop, false);
if (!$productSelected) {
$isSelected = $settingsService->isOptionSelected( $isSelected = $settingsService->isOptionSelected(
$prop, $prop,
$arIBlock['OLD_PROPERTY_SKU_SELECT'], $arIBlock['OLD_PROPERTY_SKU_SELECT'],
@ -388,7 +388,6 @@ if ($STEP === 1) {
?? $skuHlTableName; ?? $skuHlTableName;
echo $isSelected ? ' selected' : ''; echo $isSelected ? ' selected' : '';
}
?> ?>
> >
<?=$prop['NAME']?> <?=$prop['NAME']?>
@ -473,9 +472,97 @@ if ($STEP === 1) {
</tr> </tr>
<?php <?php
} ?> } ?>
<?php
$catalogId = $arIBlock['ID'];
$catalogCustomProps = $settingsService->customPropList[$catalogId];
if (!empty($catalogCustomProps)) {
foreach ($catalogCustomProps as $catalogCustomPropCode => $catalogCustomPropValue) { ?>
<tr class="adm-list-table-row custom-property-row">
<td class="adm-list-table-cell custom-property-title">
<?=htmlspecialcharsex($catalogCustomPropValue)?>
</td>
<td class="adm-list-table-cell">
<select
name="iblockPropertyProduct_<?=$catalogCustomPropCode?>[<?= $catalogId ?>]"
id="iblockPropertyProduct_<?=$catalogCustomPropCode . $catalogId ?>"
class="property-export"
data-type="<?= $catalogCustomPropCode ?>"
onchange="propertyChange(this)"
style="width: 200px">
<option value=""></option>
<?php foreach ($arIBlock['PROPERTIES_PRODUCT'] as $prop) {
$productHlTableName = ''; ?>
<option value="<?=$prop['CODE']?>"
<?php
echo $settingsService->getOptionClass($prop, true);
$isSelected = $settingsService->isOptionSelected(
$prop,
$arIBlock['OLD_PROPERTY_PRODUCT_SELECT'],
$catalogCustomPropCode
);
$productHlTableName
= $settingsService->getHlTableName($prop)
?? $productHlTableName;
echo $isSelected ? ' selected' : '';
?>
>
<?=$prop['NAME']?>
</option>
<?php } ?>
</select>
</td>
<?php if ($arIBlock['PROPERTIES_SKU'] !== null) { ?>
<td class="adm-list-table-cell">
<select
name="iblockPropertySku_<?=$catalogCustomPropCode?>[<?= $catalogId ?>]"
id="iblockPropertySku_<?=$catalogCustomPropCode . $catalogId ?>"
class="property-export"
data-type="<?= $catalogCustomPropCode ?>"
onchange="propertyChange(this)"
style="width: 200px">
<option value=""></option>
<?php foreach ($arIBlock['PROPERTIES_SKU'] as $prop) {
$skuHlTableName = ''; ?>
<option value="<?=$prop['CODE']?>"
<?php
echo $settingsService->getOptionClass($prop, false);
$isSelected = $settingsService->isOptionSelected(
$prop,
$arIBlock['OLD_PROPERTY_SKU_SELECT'],
$catalogCustomPropCode
);
$skuHlTableName
= $settingsService->getHlTableName($prop)
?? $skuHlTableName;
echo $isSelected ? ' selected' : '';
?>
>
<?=$prop['NAME']?>
</option>
<?php } ?>
</select>
<button id="delete-custom-row" class="adm-btn-save" type="button" style="margin-left: 10px"><?= GetMessage('DELETE_PROPERTY');?></button>
</td>
<?php } ?>
</tr>
<?php }
}
?>
</tbody> </tbody>
</table> </table>
<br> <button class="adm-btn-save add-custom-row" type="button" style="margin-top: 20px;"><?= GetMessage('ADD_PROPERTY');?></button>
<br> <br>
</div> </div>
</div> </div>
@ -484,6 +571,20 @@ if ($STEP === 1) {
</div> </div>
<input type="hidden" name="count_checked" id="count_checked" value="<?=$intCountChecked?>"> <input type="hidden" name="count_checked" id="count_checked" value="<?=$intCountChecked?>">
<br> <br>
<template id="custom-property-template-row">
<tr class="adm-list-table-row custom-property-row">
<td class="adm-list-table-cell">
<input type="text" title="Название нового свойства" name="custom-property-title" style="width: 200px">
</td>
<td class="adm-list-table-cell">
<select name="iblockPropertyProduct_" id="iblockPropertyProduct_" class="property-export" onchange="propertyChange(this)" style="width: 200px"></select>
</td>
<td class="adm-list-table-cell">
<select name="iblockPropertySku_" id="iiblockPropertySku_" class="property-export" onchange="propertyChange(this)" style="width: 200px"></select>
<button id="delete-new-custom-row" class="adm-btn-save" type="button" style="margin-left: 10px"><?= GetMessage('DELETE_PROPERTY');?></button>
</td>
</tr>
</template>
<h3><?=GetMessage('SETTINGS_EXPORT')?></h3> <h3><?=GetMessage('SETTINGS_EXPORT')?></h3>
<span class="text"><?=GetMessage('FILENAME')?><br><br></span> <span class="text"><?=GetMessage('FILENAME')?><br><br></span>
<input type="text" name="SETUP_FILE_NAME" value="<?=htmlspecialcharsbx(strlen($SETUP_FILE_NAME) > 0 ? <input type="text" name="SETUP_FILE_NAME" value="<?=htmlspecialcharsbx(strlen($SETUP_FILE_NAME) > 0 ?
@ -566,7 +667,7 @@ if ($STEP === 1) {
<input type="hidden" name="STEP" value="<?=$STEP + 1?>"> <input type="hidden" name="STEP" value="<?=$STEP + 1?>">
<input type="hidden" name="SETUP_FIELDS_LIST" value="<?= <input type="hidden" name="SETUP_FIELDS_LIST" value="<?=
$settingsService->getSetupFieldsString( $settingsService->getSetupFieldsString(
array_keys($settingsService->actrualPropList) ?? [], array_keys($settingsService->actualPropList) ?? [],
$hlblockModule === true, $hlblockModule === true,
$hlBlockList ?? [] $hlBlockList ?? []
) )
@ -577,8 +678,109 @@ if ($STEP === 1) {
</form> </form>
<?php CJSCore::Init(['jquery']);?> <?php CJSCore::Init(['jquery']);?>
<?php CUtil::InitJSCore(['intaro_custom_props']); ?>
<script type="text/javascript"> <script type="text/javascript">
BX.ready(function() {
if (typeof createCustomPropsRaw !== 'function') {
$('.add-custom-row').hide();
}
});
$('.add-custom-row').click(function () {
if (typeof createCustomPropsRaw === 'function') {
createCustomPropsRaw($(this));
}
});
$(document).on('click', '#delete-new-custom-row', function () {
if (typeof deleteCustomPropRow === 'function') {
deleteCustomPropRow($(this));
}
});
$(document).on('click', '#delete-custom-row', function () {
let buttonElem = $(this);
if (typeof addCustomPropToDelete === 'function' && typeof deleteCustomPropRow === 'function') {
addCustomPropToDelete(buttonElem);
deleteCustomPropRow(buttonElem);
}
});
$(document).on('blur', 'input[name="custom-property-title"]', function () {
if (typeof getUniquePropertyCode === 'function' && typeof addCustomPropCodeToSelectAttributes === 'function') {
let inputElem = $(this);
let newPropertyTitle = inputElem.val();
if (!newPropertyTitle) {
return;
}
let newPropertyCode = getUniquePropertyCode(newPropertyTitle);
addCustomPropCodeToSelectAttributes(newPropertyCode, inputElem);
}
});
$('#submit-form').submit(function (formEvent) {
if (typeof setCustomProperties !== "function") {
return;
}
formEvent.preventDefault();
let savePromise = null;
let deletePromise = null;
let formElem = formEvent.currentTarget;
let profileId = $($('input[name="PROFILE_ID"]')).val();
setCustomProperties();
if (Object.keys(customProps).length > 0) {
savePromise = BX.ajax.runAction('intaro:retailcrm.api.customexportprops.save', {
json: {
properties: customProps,
profileId: profileId
},
}).then(addParamsToSetupFieldsList());
}
if (Object.keys(customPropsToDelete).length > 0) {
deletePromise = BX.ajax.runAction('intaro:retailcrm.api.customexportprops.delete', {
json: {
properties: customPropsToDelete,
profileId: profileId
},
}).then(deleteParamsFromSetupFieldsList());
}
const promises = [savePromise, deletePromise].filter(Boolean);
if (promises.length > 0) {
Promise.all(promises)
.finally(() => {
formElem.submit();
});
} else {
formElem.submit();
}
});
const setupFieldsListElement = $('input[name="SETUP_FIELDS_LIST"]');
let customProps = {};
let customPropsToDelete = {};
const setupFieldsParamsToFill = [
'iblockPropertySku_',
'iblockPropertyUnitSku_',
'iblockPropertyProduct_',
'iblockPropertyUnitProduct_',
'highloadblockb_hlsys_marking_code_group_',
'highloadblock_productb_hlsys_marking_code_group_',
'highloadblockeshop_color_reference_',
'highloadblock_producteshop_color_reference_',
'highloadblockeshop_brand_reference_',
'highloadblock_producteshop_brand_reference_'
];
function checkLoadStatus(object) function checkLoadStatus(object)
{ {
if (object.checked) { if (object.checked) {

View File

@ -50,6 +50,10 @@ $arJsConfig = [
'js' => '/bitrix/js/intaro/sms.js', 'js' => '/bitrix/js/intaro/sms.js',
'rel' => [], 'rel' => [],
], ],
'intaro_custom_props' => [
'js' => '/bitrix/js/intaro/export/custom-props-export.js',
'rel' => [],
],
]; ];
foreach ($arJsConfig as $ext => $arExt) { foreach ($arJsConfig as $ext => $arExt) {

View File

@ -0,0 +1,228 @@
function deleteCustomPropRow(deleteButton)
{
deleteButton.closest('tr').remove();
}
function addCustomPropToDelete(deleteButton)
{
let deletedPropTitle = deleteButton.closest('td').siblings().filter('.custom-property-title').text().trim();
let deletedPropCode = deleteButton.siblings().filter('select').first().data('type');
let customPropCatalogId = deleteButton.closest('.iblockExportTable').data('type');
let values = {
'code': deletedPropCode,
'title': deletedPropTitle,
};
if (customPropsToDelete.hasOwnProperty(customPropCatalogId)) {
customPropsToDelete[customPropCatalogId].push(values);
} else {
customPropsToDelete[customPropCatalogId] = [values];
}
}
function addCustomPropCodeToSelectAttributes(customPropCode, customPropTitleElem)
{
let selectElements = customPropTitleElem.closest('.custom-property-row').find('td select');
let catalogId = customPropTitleElem.closest('.iblockExportTable').data('type');
selectElements.each(function (index, element) {
let selectElem = $(element);
let newSelectIdValue = selectElem.attr('id').match(/^[^_]*_/)[0] + customPropCode + catalogId;
let newSelectNameValue = selectElem.attr('name').match(/^[^_]*_/)[0] + customPropCode + `[${catalogId}]`;
selectElem.attr('id', newSelectIdValue);
selectElem.attr('name', newSelectNameValue);
selectElem.data('type', customPropCode);
triggerSelectChange(selectElem);
});
}
function triggerSelectChange(selectElem)
{
if (selectElem.val().length > 0) {
selectElem.trigger('change', [self]);
}
}
function setCustomProperties()
{
let customPropertiesRows = $('.custom-property-row');
if (customPropertiesRows.length === 0) {
return;
}
let customPropertyCatalogId;
let customPropertyTitle = '';
let customPropertyCode = '';
let productPropertyMatch = '';
let offerPropertyMatch = '';
let catalogIds = [];
customPropertiesRows.each(function (index, propertyRow) {
let propertyRowObj = $(propertyRow);
customPropertyCatalogId = propertyRowObj.closest('.iblockExportTable').data('type');
customPropertyTitle = propertyRowObj.find('input[name="custom-property-title"]').val();
if (!customPropertyTitle) {
return true;
}
customPropertyCode = getUniquePropertyCode(customPropertyTitle);
productPropertyMatch = propertyRowObj.find('select[name=custom-product-property-select]').val();
offerPropertyMatch = propertyRowObj.find('select[name=custom-offer-property-select]').val();
let values = {
'title': customPropertyTitle,
'code': customPropertyCode,
'productProperty': productPropertyMatch,
'offerProperty': offerPropertyMatch
};
if (catalogIds.indexOf(customPropertyCatalogId) === -1) {
customProps[customPropertyCatalogId] = [values];
} else {
customProps[customPropertyCatalogId].push(values);
}
catalogIds.push(customPropertyCatalogId);
});
}
function getUniquePropertyCode(customPropertyTitle)
{
let uniqueValue = transliterate(customPropertyTitle).replace(/ /g, '_');
let counter = 0;
const setupFieldsListValues = setupFieldsListElement.val().split(',');
while (setupFieldsListValues.includes(uniqueValue)) {
uniqueValue = `${customPropertyTitle}${++counter}`;
}
return uniqueValue;
}
function addParamsToSetupFieldsList()
{
let newParams = '';
if (Object.keys(customProps).length === 0) {
return;
}
for (let propertiesByCatalogId of Object.values(customProps)) {
propertiesByCatalogId.forEach(function (values) {
setupFieldsParamsToFill.forEach(function (param) {
newParams += ',' + param + values.code;
});
});
}
let newValue = setupFieldsListElement.val() + newParams;
setupFieldsListElement.val(newValue);
return true;
}
function deleteParamsFromSetupFieldsList()
{
let setupFields = setupFieldsListElement.val();
if (Object.keys(customPropsToDelete).length === 0) {
return;
}
for (let propsByCatalogId of Object.values(customPropsToDelete)) {
propsByCatalogId.forEach(function (propValues) {
setupFieldsParamsToFill.forEach(function (param) {
let paramToDelete = ',' + param + propValues.code;
setupFields = setupFields.replace(paramToDelete, '');
});
});
}
setupFieldsListElement.val(setupFields);
return true;
}
function createCustomPropsRaw(addRowButton)
{
let templateRow = $($('#custom-property-template-row').html());
let templateSelectElements = templateRow.find('select');
let prevTableRow = $(addRowButton).prev('table').find('tbody tr:last-child');
let lastRawSelectElements = prevTableRow.find('td select');
lastRawSelectElements.each(function (index, element) {
let selectElement = $(element);
let templateSelectElement = templateSelectElements[index];
fillTemplateSelect(selectElement, templateSelectElement);
prevTableRow.after(templateRow);
});
}
function fillTemplateSelect(sourceSelectElement, templateSelectElement)
{
let selectOptions = sourceSelectElement.find('option');
selectOptions.each(function (index, element) {
let optionElem = $(element);
let value = $(optionElem).val();
let text = $(optionElem).text();
$('<option>', { value: value, text: text }).appendTo(templateSelectElement);
});
}
function transliterate(titleToTransliterate)
{
const hasCyrillicChars = /[\u0400-\u04FF]/.test(titleToTransliterate);
if (!hasCyrillicChars) {
return titleToTransliterate;
}
translitedText = '';
for (var i = 0; i < titleToTransliterate.length; i++) {
switch (titleToTransliterate[i]) {
case 'а': case 'А': translitedText += 'a'; break;
case 'б': case 'Б': translitedText += 'b'; break;
case 'в': case 'В': translitedText += 'v'; break;
case 'г': case 'Г': translitedText += 'g'; break;
case 'д': case 'Д': translitedText += 'd'; break;
case 'е': case 'Е': translitedText += 'e'; break;
case 'ё': case 'Ё': translitedText += 'yo'; break;
case 'ж': case 'Ж': translitedText += 'zh'; break;
case 'з': case 'З': translitedText += 'z'; break;
case 'и': case 'И': translitedText += 'i'; break;
case 'й': case 'Й': translitedText += 'y'; break;
case 'к': case 'К': translitedText += 'k'; break;
case 'л': case 'Л': translitedText += 'l'; break;
case 'м': case 'М': translitedText += 'm'; break;
case 'н': case 'Н': translitedText += 'n'; break;
case 'о': case 'О': translitedText += 'o'; break;
case 'п': case 'П': translitedText += 'p'; break;
case 'р': case 'Р': translitedText += 'r'; break;
case 'с': case 'С': translitedText += 's'; break;
case 'т': case 'Т': translitedText += 't'; break;
case 'у': case 'У': translitedText += 'u'; break;
case 'ф': case 'Ф': translitedText += 'f'; break;
case 'х': case 'Х': translitedText += 'h'; break;
case 'ц': case 'Ц': translitedText += 'c'; break;
case 'ч': case 'Ч': translitedText += 'ch'; break;
case 'ш': case 'Ш': translitedText += 'sh'; break;
case 'щ': case 'Щ': translitedText += 'sch'; break;
case 'ъ': case 'Ъ': translitedText += ''; break;
case 'ы': case 'Ы': translitedText += 'y'; break;
case 'ь': case 'Ь': translitedText += ''; break;
case 'э': case 'Э': translitedText += 'e'; break;
case 'ю': case 'Ю': translitedText += 'yu'; break;
case 'я': case 'Я': translitedText += 'ya'; break;
default: translitedText += titleToTransliterate[i]; break;
}
}
return translitedText;
}

View File

@ -1,6 +1,6 @@
<?php <?php
$arModuleVersion = [ $arModuleVersion = [
'VERSION' => '6.5.39', 'VERSION' => '6.6.0',
'VERSION_DATE' => '2024-12-08 18:30:00' 'VERSION_DATE' => '2024-12-09 14:00:00'
]; ];

View File

@ -47,3 +47,5 @@ $MESS['BASE_PRICE'] = 'Base price';
$MESS['WAIT'] = 'Loading...'; $MESS['WAIT'] = 'Loading...';
$MESS["OFFERS_VALUE"] = "Maximum number of trade offers for a product"; $MESS["OFFERS_VALUE"] = "Maximum number of trade offers for a product";
$MESS["LOAD_NON_ACTIVITY"] = "Unload inactive products, services and trade offers"; $MESS["LOAD_NON_ACTIVITY"] = "Unload inactive products, services and trade offers";
$MESS["ADD_PROPERTY"] = "Add property";
$MESS["DELETE_PROPERTY"] = "Delete";

View File

@ -47,3 +47,5 @@ $MESS["UNIT_MEASUREMENT_KG"] = "кг.";
$MESS['BASE_PRICE'] = 'Базовая цена'; $MESS['BASE_PRICE'] = 'Базовая цена';
$MESS['WAIT'] = 'Загрузка...'; $MESS['WAIT'] = 'Загрузка...';
$MESS["OFFERS_VALUE"] = "Максимальное количество торговых предложений у товара"; $MESS["OFFERS_VALUE"] = "Максимальное количество торговых предложений у товара";
$MESS["ADD_PROPERTY"] = "Добавить свойство";
$MESS["DELETE_PROPERTY"] = "Удалить";

View File

@ -24,15 +24,19 @@ trait InstallerTrait
false false
); );
$path = $_SERVER['DOCUMENT_ROOT'] . '/local/'; $pathFrom = $_SERVER['DOCUMENT_ROOT'] .
'/bitrix/modules/' .
Constants::MODULE_ID .
'/install/export/bitrix/js/intaro/export'
;
CheckDirPath($path); CopyDirFiles(
$pathFrom,
$file = new \Bitrix\Main\IO\File($path . 'icml_property_retailcrm.txt', $siteId = null); $_SERVER['DOCUMENT_ROOT'] . '/bitrix/js/intaro/export/',
true,
if (!$file->isExists()) { true,
$file->putContents(""); false
} );
} }
public function subscriptionSetup() public function subscriptionSetup()

View File

@ -0,0 +1,95 @@
<?php
namespace Intaro\RetailCrm\Controller;
use Bitrix\Main\Engine\Controller;
use Bitrix\Main\Result;
use Bitrix\Main\Error;
use Bitrix\Main\Application;
use Intaro\RetailCrm\Icml\SettingsService;
/**
* @category Integration
* @package Intaro\RetailCrm\Controller
* @author RetailCRM <integration@retailcrm.ru>
* @license MIT
* @link http://retailcrm.ru
* @see http://retailcrm.ru/docs
*/
class CustomExportProps extends Controller
{
private function getRequestData(): array
{
$data = $this->getRequest()->getInput();
if ($data === null) {
}
return json_decode($data, true);
}
public function saveAction()
{
$requestData = $this->getRequestData();
$props = $requestData['properties'];
$profileId = $requestData['profileId'];
$settingsService = SettingsService::getInstance(
[],
null,
$profileId
);
$idCategories = array_keys($props);
if ($idCategories !== []) {
$settingsService->setProfileCatalogs($idCategories);//сразу заменяет и create и update
}
foreach ($props as $catalogId => $propsArray) {
$catalogCustomProps = [];
foreach ($propsArray as $property) {
$catalogCustomProps[] = [
'code' => $property['code'],
'title' => $property['title']
];
}
$settingsService
->setCatalogCustomPropsOptionName($catalogId)
->saveCustomProps($catalogCustomProps)
;
}
}
public function deleteAction()
{
$requestData = $this->getRequestData();
$props = $requestData['properties'];
$profileId = $requestData['profileId'];
$settingsService = SettingsService::getInstance(
[],
null,
$profileId
);
foreach ($props as $catalogId => $propsArray) {
$catalogCustomProps = [];
foreach ($propsArray as $property) {
$catalogCustomProps[] = [
'code' => $property['code'],
'title' => $property['title']
];
}
$settingsService
->setCatalogCustomPropsOptionName($catalogId)
->removeCustomProps($catalogCustomProps, $catalogId)
;
}
}
}

View File

@ -8,14 +8,19 @@ use CCatalogGroup;
use CCatalogSku; use CCatalogSku;
use CCatalogVat; use CCatalogVat;
use CIBlock; use CIBlock;
use COption;
use Bitrix\Main\Config\Option;
use Intaro\RetailCrm\Service\Hl; use Intaro\RetailCrm\Service\Hl;
use RetailcrmConfigProvider; use RetailcrmConfigProvider;
use Bitrix\Main\Application;
use Bitrix\Main\Entity\Query;
/** /**
* Отвечает за управление настройками выгрузки icml каталога * Отвечает за управление настройками выгрузки icml каталога
* * @var $PROFILE_ID - зачем если получаем в в конструкторе инстансе
* Class SettingsService * Class SettingsService
* *
*
* @package Intaro\RetailCrm\Icml * @package Intaro\RetailCrm\Icml
*/ */
class SettingsService class SettingsService
@ -32,6 +37,14 @@ class SettingsService
*/ */
public const INFOBLOCK_WITH_SKU = 'P'; public const INFOBLOCK_WITH_SKU = 'P';
private const MODULE_ID = 'intaro.retailcrm';
private string $catalogCustomPropsOptionName;
private string $profileCatalogsOptionName;
private string $exportProfileId;
/** /**
* @var array * @var array
*/ */
@ -98,7 +111,13 @@ class SettingsService
public $loadNonActivity; public $loadNonActivity;
/** @var array */ /** @var array */
public $actrualPropList = []; public $actualPropList = [];
/** @var array */
public $customPropList = [];
/** @var array */
public $defaultPropList = [];
/** /**
* @var \Intaro\RetailCrm\Icml\SettingsService|null * @var \Intaro\RetailCrm\Icml\SettingsService|null
@ -112,7 +131,7 @@ class SettingsService
* @param array $arOldSetupVars * @param array $arOldSetupVars
* @param string|null $action * @param string|null $action
*/ */
private function __construct(array $arOldSetupVars, ?string $action) private function __construct(array $arOldSetupVars, ?string $action, ?string $profileId)
{ {
$this->arOldSetupVars = $arOldSetupVars; $this->arOldSetupVars = $arOldSetupVars;
$this->action = $action; $this->action = $action;
@ -128,7 +147,15 @@ class SettingsService
$this->getPriceTypes(); $this->getPriceTypes();
$this->getVatRates(); $this->getVatRates();
$this->actrualPropList = array_merge($this->getIblockPropsPreset(), $this->parseNewProps()); $this->exportProfileId = $profileId ?? '0';
$this->profileCatalogsOptionName = sprintf('exportProfileId_%s_catalogs', $this->exportProfileId);
$this->linkNewProfile();
$this->deleteEmptyProfileCatalogs();
$this->customPropList = $this->getNewProps();
$this->defaultPropList = $this->getIblockPropsPreset();
$this->actualPropList = $this->getActualPropList();
} }
/** /**
@ -137,16 +164,30 @@ class SettingsService
* *
* @return \Intaro\RetailCrm\Icml\SettingsService|null * @return \Intaro\RetailCrm\Icml\SettingsService|null
*/ */
public static function getInstance(array $arOldSetupVars, ?string $action): ?SettingsService public static function getInstance(array $arOldSetupVars, ?string $action, ?string $profileId): ?SettingsService
{ {
if (is_null(self::$instance)) { if (is_null(self::$instance)) {
self::$instance = new self($arOldSetupVars, $action); self::$instance = new self($arOldSetupVars, $action, $profileId);
} }
return self::$instance; return self::$instance;
} }
public function getPriceTypes() private function getActualPropList(): array
{
$customProps = [];
foreach ($this->customPropList as $propsByCatalog) {
$customProps = array_merge($customProps, $propsByCatalog);
}
return [
...$this->defaultPropList,
...$customProps
];
}
private function getPriceTypes()
{ {
$dbPriceType = CCatalogGroup::GetList(['SORT' => 'ASC'], [], [], [], ['ID', 'NAME', 'BASE']); $dbPriceType = CCatalogGroup::GetList(['SORT' => 'ASC'], [], [], [], ['ID', 'NAME', 'BASE']);
@ -155,7 +196,7 @@ class SettingsService
} }
} }
public function getVatRates() private function getVatRates()
{ {
$dbVatRate = CCatalogVat::GetListEx(['SORT' => 'ASC'], ['ACTIVE' => 'Y'], false, false, ['ID', 'NAME', 'RATE']); $dbVatRate = CCatalogVat::GetListEx(['SORT' => 'ASC'], ['ACTIVE' => 'Y'], false, false, ['ID', 'NAME', 'RATE']);
@ -230,9 +271,11 @@ class SettingsService
private function setProperties(array &$properties, string $propName): void private function setProperties(array &$properties, string $propName): void
{ {
foreach ($this->arOldSetupVars[$propName] as $iblock => $val) { foreach ($this->arOldSetupVars[$propName] as $iblock => $val) {
if (!empty($val)) {
$properties[$iblock][$propName] = $val; $properties[$iblock][$propName] = $val;
} }
} }
}
/** /**
* @return mixed|string * @return mixed|string
@ -260,27 +303,22 @@ class SettingsService
]; ];
} }
private function parseNewProps(): array private function getNewProps(): array
{ {
global $APPLICATION;
$result = []; $result = [];
$text = $APPLICATION->GetFileContent($_SERVER["DOCUMENT_ROOT"] . "/local/icml_property_retailcrm.txt"); $currentProfileCatalogIds = $this->getProfileCatalogs();
if ($text === false) { if (!is_null($currentProfileCatalogIds)) {
return $result; foreach ($currentProfileCatalogIds as $catalogId) {
$catalogCustomProps = $this
->setCatalogCustomPropsOptionName($catalogId)
->getCustomProps()
;
foreach ($catalogCustomProps as $prop) {
$result[$catalogId][$prop['code']] = $prop['title'];
} }
preg_match_all('/\w+\s*=\s*\w+[ *\w+]*/mu', $text, $matches);
foreach ($matches[0] as $newProp) {
$elements = explode("=", $newProp);
if (empty($elements[0]) || empty($elements[1])) {
continue;
} }
$result[trim($elements[0])] = trim($elements[1]);
} }
return $result; return $result;
@ -289,7 +327,7 @@ class SettingsService
/** /**
* @return array * @return array
*/ */
public function getHintProps(): array private function getHintProps(): array
{ {
return [ return [
'article' => ['ARTICLE', 'ART', 'ARTNUMBER', 'ARTICUL', 'ARTIKUL'], 'article' => ['ARTICLE', 'ART', 'ARTNUMBER', 'ARTICUL', 'ARTIKUL'],
@ -365,7 +403,7 @@ class SettingsService
public function setProps(): void public function setProps(): void
{ {
foreach (array_keys($this->actrualPropList) as $prop) { foreach (array_keys($this->actualPropList) as $prop) {
$this->setProperties($this->iblockPropertySku, 'iblockPropertySku_' . $prop); $this->setProperties($this->iblockPropertySku, 'iblockPropertySku_' . $prop);
$this->setProperties($this->iblockPropertyUnitSku, 'iblockPropertyUnitSku_' . $prop); $this->setProperties($this->iblockPropertyUnitSku, 'iblockPropertyUnitSku_' . $prop);
$this->setProperties($this->iblockPropertyProduct, 'iblockPropertyProduct_' . $prop); $this->setProperties($this->iblockPropertyProduct, 'iblockPropertyProduct_' . $prop);
@ -548,7 +586,7 @@ class SettingsService
* *
* @return array * @return array
*/ */
public function getSiteList(int $iblockId): array private function getSiteList(int $iblockId): array
{ {
$siteList = []; $siteList = [];
@ -576,9 +614,11 @@ class SettingsService
$dbSkuProperties = CIBlock::GetProperties($iblockOffer['IBLOCK_ID'], [], ['MULTIPLE' => 'N']); $dbSkuProperties = CIBlock::GetProperties($iblockOffer['IBLOCK_ID'], [], ['MULTIPLE' => 'N']);
while ($prop = $dbSkuProperties->Fetch()) { while ($prop = $dbSkuProperties->Fetch()) {
if ($prop['CODE'] !== '') {
$propertiesSKU[] = $prop; $propertiesSKU[] = $prop;
} }
} }
}
return $propertiesSKU; return $propertiesSKU;
} }
@ -597,7 +637,7 @@ class SettingsService
$props = []; $props = [];
if (isset($oldValues[$iblockId])) { if (isset($oldValues[$iblockId])) {
foreach (array_keys($this->actrualPropList) as $prop) { foreach (array_keys($this->actualPropList) as $prop) {
$fullKey = $keyGroup . '_' . $prop; $fullKey = $keyGroup . '_' . $prop;
$props[$prop] = $oldValues[$iblockId][$fullKey]; $props[$prop] = $oldValues[$iblockId][$fullKey];
} }
@ -623,15 +663,17 @@ class SettingsService
* *
* @return array|null * @return array|null
*/ */
public function getProductProps(int $iblockId): ?array private function getProductProps(int $iblockId): ?array
{ {
$propertiesProduct = null; $propertiesProduct = null;
$iblockResult = CIBlock::GetProperties($iblockId, [], ['MULTIPLE' => 'N']); $iblockResult = CIBlock::GetProperties($iblockId, [], ['MULTIPLE' => 'N']);
while ($prop = $iblockResult->Fetch()) { while ($prop = $iblockResult->Fetch()) {
if ($prop['CODE'] !== '') {
$propertiesProduct[] = $prop; $propertiesProduct[] = $prop;
} }
}
return $propertiesProduct; return $propertiesProduct;
} }
@ -642,7 +684,7 @@ class SettingsService
* *
* @return bool * @return bool
*/ */
public function isExport($iblockId, $iblockExport): bool private function isExport($iblockId, $iblockExport): bool
{ {
if (is_array($iblockExport) && count($iblockExport) !== 0) { if (is_array($iblockExport) && count($iblockExport) !== 0) {
return (in_array($iblockId, $iblockExport)); return (in_array($iblockId, $iblockExport));
@ -747,4 +789,152 @@ class SettingsService
return [$arIBlockList, $intCountChecked, $intCountAvailIBlock, $arIBlockList['iblockExport'] ?? false]; return [$arIBlockList, $intCountChecked, $intCountAvailIBlock, $arIBlockList['iblockExport'] ?? false];
} }
public function setCatalogCustomPropsOptionName(string $catalogId): self
{
$this->catalogCustomPropsOptionName = sprintf(
'exportCustomProps_ProfileId_%s_catalogId_%s',
$this->exportProfileId,
$catalogId
);
return $this;
}
private function getCustomProps(): ?array
{
$props = unserialize(COption::GetOptionString(self::MODULE_ID, $this->catalogCustomPropsOptionName));
if (!$props) {
return null;
}
return $props;
}
public function removeCustomProps(array $propsToDelete, string $catalogId): void
{
$currentCatalogProps = $this->getCustomProps();
$updatedCatalogProps = array_values(
array_filter(
$currentCatalogProps,
fn ($currentProp) => !in_array($currentProp, $propsToDelete)
));
if (empty($updatedCatalogProps)) {
$this->deleteOptionEntry($this->catalogCustomPropsOptionName);
$this->deleteProfileCatalog($catalogId);
} else {
$this->updateCustomProps($updatedCatalogProps);
}
}
private function updateCustomProps(array $updatedProps)
{
$serializedProps = serialize($updatedProps);
$this->updateOptionEntry($this->catalogCustomPropsOptionName, $serializedProps);
}
private function setCustomProps(array $props)
{
$propsString = serialize($props);
$this->setOptionEntry($this->catalogCustomPropsOptionName, $propsString);
}
public function saveCustomProps(array $newProps): void
{
$currentProps = $this->getCustomProps();
if (is_null($currentProps)) {
$this->setCustomProps($newProps);
} else {
$updatedProps = array_merge($currentProps, $newProps);
$this->updateCustomProps($updatedProps);
}
}
private function getProfileCatalogs(): ?array
{
$catalogs = unserialize(COption::GetOptionString(self::MODULE_ID, $this->profileCatalogsOptionName));
if (!$catalogs) {
return null;
}
return $catalogs;
}
public function setProfileCatalogs(array $catalogsId): void
{
$catalogs = serialize($catalogsId);
$this->setOptionEntry($this->profileCatalogsOptionName, $catalogs);
}
private function deleteProfileCatalog(string $catalogId): void
{
$currentCatalogs = $this->getProfileCatalogs();
$catalogIdIndex = array_search($catalogId, $currentCatalogs);
if ($catalogIdIndex !== false) {
unset($currentCatalogs[$catalogIdIndex]);
}
$updatedCatalogs = serialize($currentCatalogs);
$this->updateOptionEntry($this->profileCatalogsOptionName, $updatedCatalogs);
}
private function deleteEmptyProfileCatalogs(): void
{
$currentCatalogs = $this->getProfileCatalogs();
if (is_null($currentCatalogs)) {
$this->deleteOptionEntry($this->profileCatalogsOptionName);
}
}
private function setOptionEntry(string $name, string $value)
{
COption::SetOptionString(self::MODULE_ID, $name, $value);
}
private function updateOptionEntry(string $name, string $value)
{
COption::SetOptionString(self::MODULE_ID, $name, '');
COption::SetOptionString(self::MODULE_ID, $name, $value);
}
private function deleteOptionEntry(string $name)
{
COption::RemoveOption(self::MODULE_ID, $name);
}
private function linkNewProfile(): void
{
$currentProfileCatalogs = unserialize(COption::GetOptionString(self::MODULE_ID, $this->profileCatalogsOptionName));
if (!$currentProfileCatalogs) {
$tmpProfileName = 'exportProfileId_0_catalogs';
$currentProfileCatalogs = unserialize(COption::GetOptionString(self::MODULE_ID, $tmpProfileName));
if ($currentProfileCatalogs) {
$this->setOptionEntry($this->profileCatalogsOptionName, serialize($currentProfileCatalogs));
$this->deleteOptionEntry($tmpProfileName);
}
}
foreach ($currentProfileCatalogs as $catalogId) {
$optionName = sprintf('exportCustomProps_ProfileId_%s_catalogId_%s', $this->exportProfileId, $catalogId);
$propsCatalog = unserialize(COption::GetOptionString(self::MODULE_ID, $optionName));
if (!$propsCatalog) {
$tmpOptionName = sprintf('exportCustomProps_ProfileId_%s_catalogId_%s', '0', $catalogId);
$propsCatalog = unserialize(COption::GetOptionString(self::MODULE_ID, $tmpOptionName));
if ($propsCatalog) {
$this->setOptionEntry($optionName, serialize($propsCatalog));
$this->deleteOptionEntry($tmpOptionName);
}
}
}
}
} }

View File

@ -115,8 +115,8 @@ class XmlOfferBuilder
public function __construct(XmlSetup $setup, array $measure, ?string $serverName) public function __construct(XmlSetup $setup, array $measure, ?string $serverName)
{ {
$this->setup = $setup; $this->setup = $setup;
$this->setSettingsService();
$this->purchasePriceNull = RetailcrmConfigProvider::getCrmPurchasePrice(); $this->purchasePriceNull = RetailcrmConfigProvider::getCrmPurchasePrice();
$this->settingsService = SettingsService::getInstance([], '');
$this->vatRates = $this->settingsService->vatRates; $this->vatRates = $this->settingsService->vatRates;
$this->measures = $this->prepareMeasures($measure); $this->measures = $this->prepareMeasures($measure);
$this->serverName = $serverName; $this->serverName = $serverName;
@ -141,6 +141,12 @@ class XmlOfferBuilder
return $this->xmlOffer; return $this->xmlOffer;
} }
private function setSettingsService(): void
{
global $PROFILE_ID;
$this->settingsService = SettingsService::getInstance([], '', $PROFILE_ID);
}
/** /**
* @param array $categories * @param array $categories
*/ */
@ -479,7 +485,7 @@ class XmlOfferBuilder
private function createParamObject(array $params): array private function createParamObject(array $params): array
{ {
$offerParams = []; $offerParams = [];
$names = $this->settingsService->actrualPropList; $names = $this->settingsService->actualPropList;
foreach ($params as $code => $value) { foreach ($params as $code => $value) {

View File

@ -1187,41 +1187,22 @@ function update()
Loader::includeModule('highloadblock'); Loader::includeModule('highloadblock');
COption::SetOptionString('intaro.retailcrm', 'api_version', 'v5'); COption::SetOptionString('intaro.retailcrm', 'api_version', 'v5');
$orderDischarge = Option::get('intaro.retailcrm', 'order_discharge'); loadJsExport();
if ($orderDischarge === '0') {
$dateAgent = new DateTime();
$dateAgent->add('PT60S');
CAgent::AddAgent(
'RCrmActions::uploadOrdersAgent();',
'intaro.retailcrm',
'N',
180,
$dateAgent->format('d.m.Y H:i:s'),
'Y',
$dateAgent->format('d.m.Y H:i:s'),
30
);
COption::SetOptionString('intaro.retailcrm', 'order_discharge', '2');
}
} }
function createCustomPropertyFile() function loadJsExport()
{ {
$path = $_SERVER['DOCUMENT_ROOT'] . '/local/'; $pathFrom = $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/intaro.retailcrm/install/export/bitrix/js/intaro/export';
CheckDirPath($path); CopyDirFiles(
$pathFrom,
$file = new \Bitrix\Main\IO\File($path . 'icml_property_retailcrm.txt', $siteId = null); $_SERVER['DOCUMENT_ROOT'] . '/bitrix/js/intaro/export/',
true,
if (!$file->isExists()) { true,
$file->putContents(""); false
} );
} }
try { try {
update(); update();
} catch (Main\ObjectPropertyException | Main\ArgumentException | Main\SystemException $exception) { } catch (Main\ObjectPropertyException | Main\ArgumentException | Main\SystemException $exception) {

View File

@ -17,25 +17,6 @@ class SettingServiceTest extends \BitrixTestCase
; ;
} }
public function testConstruct(): SettingsService
{
$path = $_SERVER['DOCUMENT_ROOT'] . '/local/';
CheckDirPath($path);
$file = new \Bitrix\Main\IO\File($path . '/icml_property_retailcrm.txt', $siteId = null);
$file->putContents("property1 = test prop \n property2 = test prop 2");
$settingService = SettingsService::getInstance($this->getSetupVars(), "");
$this->assertInstanceOf(SettingsService::class, $settingService);
$this->assertArrayHasKey('property1', $settingService->actrualPropList);
$this->assertArrayHasKey('property2', $settingService->actrualPropList);
return $settingService;
}
private function getSetupVars() private function getSetupVars()
{ {
return [ return [