feat: add product interaction mode selector with three scenarios

- Add ItemToggleButton component for 3-way toggle in admin panel
- Add product_interaction_mode setting with options: order, manager, browser
- Add manager_username setting for Telegram manager contact
- Remove store_enabled setting, replaced with product_interaction_mode
- Create migration to automatically migrate store_enabled to product_interaction_mode
- Update Product.vue to handle all three interaction modes
- Update Dock.vue to show cart button only when product_interaction_mode is 'order'
- Rename 'Магазин' tab to 'Витрина' in admin panel
- Remove 'Разрешить покупки' option (now controlled via product_interaction_mode)
- Set default product_interaction_mode to 'browser'
- Update StoreDTO to remove enableStore field
- Update SettingsHandler to return product_interaction_mode instead of store_enabled
This commit is contained in:
2025-12-25 18:02:38 +03:00
parent 22ddc3043d
commit ecf4df363d
13 changed files with 228 additions and 29 deletions

View File

@@ -52,10 +52,11 @@ HTML,
],
'store' => [
'enable_store' => true,
'feature_coupons' => true,
'feature_vouchers' => true,
'show_category_products_button' => true,
'product_interaction_mode' => 'browser',
'manager_username' => null,
],
'texts' => [

View File

@@ -0,0 +1,54 @@
<?php
use Openguru\OpenCartFramework\Migrations\Migration;
use Openguru\OpenCartFramework\OpenCart\Decorators\OcRegistryDecorator;
use Openguru\OpenCartFramework\Support\Arr;
return new class extends Migration {
public function up(): void
{
$opencart = $this->app->get(OcRegistryDecorator::class);
$opencart->load->model('setting/setting');
$currentSettings = $opencart->model_setting_setting->getSetting('module_telecart');
if (! $currentSettings || ! isset($currentSettings['module_telecart_settings'])) {
$this->logger->info("Settings not found in database, migration skipped");
return;
}
$allSettings = $currentSettings['module_telecart_settings'];
// Проверяем наличие store.enable_store
$enableStore = Arr::get($allSettings, 'store.enable_store');
if ($enableStore !== null) {
// Определяем значение product_interaction_mode на основе store.enable_store
$productInteractionMode = filter_var($enableStore, FILTER_VALIDATE_BOOLEAN)
? 'order'
: 'browser';
// Устанавливаем product_interaction_mode, если его еще нет
if (!isset($allSettings['store']['product_interaction_mode'])) {
Arr::set($allSettings, 'store.product_interaction_mode', $productInteractionMode);
$this->logger->info("Migrated store.enable_store to product_interaction_mode: {$enableStore} -> {$productInteractionMode}");
} else {
$this->logger->info("product_interaction_mode already exists, skipping migration but removing store.enable_store");
}
// Удаляем store.enable_store из настроек
Arr::unset($allSettings, 'store.enable_store');
$this->logger->info("Removed store.enable_store from settings");
// Сохраняем обновленные настройки через OpenCart модель
$opencart->model_setting_setting->editSetting('module_telecart', [
'module_telecart_settings' => $allSettings,
]);
$this->logger->info("Successfully migrated store.enable_store to product_interaction_mode and removed store.enable_store from settings");
} else {
$this->logger->info("store.enable_store not found in settings, migration skipped");
}
}
};

View File

@@ -4,37 +4,35 @@ namespace App\DTO\Settings;
final class StoreDTO
{
private bool $enableStore;
private bool $featureCoupons;
private bool $featureVouchers;
private bool $showCategoryProductsButton;
private string $productInteractionMode;
private ?string $managerUsername;
private string $ocDefaultCurrency;
private bool $ocConfigTax;
private int $ocStoreId;
public function __construct(
bool $enableStore,
bool $featureCoupons,
bool $featureVouchers,
bool $showCategoryProductsButton,
string $productInteractionMode,
?string $managerUsername,
string $ocDefaultCurrency,
bool $ocConfigTax,
int $ocStoreId
) {
$this->enableStore = $enableStore;
$this->featureCoupons = $featureCoupons;
$this->featureVouchers = $featureVouchers;
$this->showCategoryProductsButton = $showCategoryProductsButton;
$this->productInteractionMode = $productInteractionMode;
$this->managerUsername = $managerUsername;
$this->ocDefaultCurrency = $ocDefaultCurrency;
$this->ocConfigTax = $ocConfigTax;
$this->ocStoreId = $ocStoreId;
}
public function isEnableStore(): bool
{
return $this->enableStore;
}
public function isFeatureCoupons(): bool
{
return $this->featureCoupons;
@@ -50,6 +48,16 @@ final class StoreDTO
return $this->showCategoryProductsButton;
}
public function getProductInteractionMode(): string
{
return $this->productInteractionMode;
}
public function getManagerUsername(): ?string
{
return $this->managerUsername;
}
public function getOcDefaultCurrency(): string
{
return $this->ocDefaultCurrency;
@@ -68,10 +76,12 @@ final class StoreDTO
public function toArray(): array
{
return [
'enable_store' => $this->enableStore,
// enable_store больше не сериализуется, так как заменен на product_interaction_mode
'feature_coupons' => $this->featureCoupons,
'feature_vouchers' => $this->featureVouchers,
'show_category_products_button' => $this->showCategoryProductsButton,
'product_interaction_mode' => $this->productInteractionMode,
'manager_username' => $this->managerUsername,
'oc_default_currency' => $this->ocDefaultCurrency,
'oc_config_tax' => $this->ocConfigTax,
'oc_store_id' => $this->ocStoreId,

View File

@@ -45,7 +45,8 @@ class SettingsHandler
'theme_dark' => $appConfig->getThemeDark(),
'ya_metrika_enabled' => $this->settings->config()->getMetrics()->isYandexMetrikaEnabled(),
'app_enabled' => $appConfig->isAppEnabled(),
'store_enabled' => $this->settings->config()->getStore()->isEnableStore(),
'product_interaction_mode' => $this->settings->config()->getStore()->getProductInteractionMode(),
'manager_username' => $this->settings->config()->getStore()->getManagerUsername(),
'feature_coupons' => $this->settings->config()->getStore()->isFeatureCoupons(),
'feature_vouchers' => $this->settings->config()->getStore()->isFeatureVouchers(),
'show_category_products_button' => $this->settings->config()->getStore()->isShowCategoryProductsButton(),

View File

@@ -138,10 +138,11 @@ class SettingsSerializerService
}
return new StoreDTO(
$data['enable_store'] ?? true,
$data['feature_coupons'] ?? true,
$data['feature_vouchers'] ?? true,
$data['show_category_products_button'] ?? true,
$data['product_interaction_mode'] ?? 'browser',
$data['manager_username'] ?? null,
$data['oc_default_currency'],
$data['oc_config_tax'],
$data['oc_store_id']
@@ -270,9 +271,7 @@ class SettingsSerializerService
private function validateStore(array $data): void
{
if (isset($data['enable_store']) && ! is_bool($data['enable_store'])) {
throw new InvalidArgumentException('store.enable_store must be a boolean');
}
// enable_store больше не валидируется, так как заменен на product_interaction_mode
if (isset($data['feature_coupons']) && ! is_bool($data['feature_coupons'])) {
throw new InvalidArgumentException('store.feature_coupons must be a boolean');
@@ -286,6 +285,25 @@ class SettingsSerializerService
throw new InvalidArgumentException('store.show_category_products_button must be a boolean');
}
if (isset($data['product_interaction_mode']) && ! is_string($data['product_interaction_mode'])) {
throw new InvalidArgumentException('store.product_interaction_mode must be a string');
}
if (isset($data['product_interaction_mode']) && ! in_array($data['product_interaction_mode'], ['order', 'manager', 'browser'], true)) {
throw new InvalidArgumentException('store.product_interaction_mode must be one of: order, manager, browser');
}
if (isset($data['manager_username']) && $data['manager_username'] !== null) {
if (! is_string($data['manager_username'])) {
throw new InvalidArgumentException('store.manager_username must be a string or null');
}
// Проверяем, что это username (не числовой ID)
$managerUsername = trim($data['manager_username']);
if ($managerUsername !== '' && preg_match('/^-?\d+$/', $managerUsername)) {
throw new InvalidArgumentException('store.manager_username must be a username (e.g., @username), not a numeric ID');
}
}
if (! isset($data['oc_default_currency'])) {
throw new InvalidArgumentException('store.oc_default_currency is required');
}