379 lines
12 KiB
PHP
379 lines
12 KiB
PHP
<?php
|
||
|
||
namespace App\Services;
|
||
|
||
use CodeIgniter\Config\BaseConfig;
|
||
|
||
/**
|
||
* Сервис для работы с подписками организаций на модули
|
||
*
|
||
* Предоставляет API для проверки доступности модулей,
|
||
* управления подписками и получения информации о модулях.
|
||
*/
|
||
class ModuleSubscriptionService
|
||
{
|
||
/**
|
||
* Конфигурация модулей (базовые значения)
|
||
*/
|
||
protected array $modulesConfig = [
|
||
'base' => [
|
||
'name' => 'Базовый модуль',
|
||
'description' => 'Основные функции',
|
||
'price_monthly' => 0,
|
||
'price_yearly' => 0,
|
||
'trial_days' => 0,
|
||
],
|
||
'crm' => [
|
||
'name' => 'CRM',
|
||
'description' => 'Управление клиентами и сделками',
|
||
'price_monthly' => 990,
|
||
'price_yearly' => 9900,
|
||
'trial_days' => 14,
|
||
],
|
||
'booking' => [
|
||
'name' => 'Бронирования',
|
||
'description' => 'Управление бронированиями',
|
||
'price_monthly' => 1490,
|
||
'price_yearly' => 14900,
|
||
'trial_days' => 14,
|
||
],
|
||
'tasks' => [
|
||
'name' => 'Задачи',
|
||
'description' => 'Управление задачами',
|
||
'price_monthly' => 790,
|
||
'price_yearly' => 7900,
|
||
'trial_days' => 14,
|
||
],
|
||
'proof' => [
|
||
'name' => 'Proof',
|
||
'description' => 'Согласование документов',
|
||
'price_monthly' => 590,
|
||
'price_yearly' => 5900,
|
||
'trial_days' => 14,
|
||
],
|
||
];
|
||
|
||
protected $db;
|
||
protected string $moduleSettingsTable = 'module_settings';
|
||
protected string $subscriptionsTable = 'organization_subscriptions';
|
||
|
||
public function __construct()
|
||
{
|
||
$this->db = \Config\Database::connect();
|
||
}
|
||
|
||
/**
|
||
* Получение базовой конфигурации модуля
|
||
*/
|
||
public function getModuleConfig(string $moduleCode): ?array
|
||
{
|
||
return $this->modulesConfig[$moduleCode] ?? null;
|
||
}
|
||
|
||
/**
|
||
* Получение всех модулей (с учётом переопределений из БД)
|
||
*/
|
||
public function getAllModules(): array
|
||
{
|
||
$modules = $this->modulesConfig;
|
||
|
||
$builder = $this->db->table($this->moduleSettingsTable);
|
||
$settings = $builder->get()->getResultArray();
|
||
|
||
foreach ($settings as $setting) {
|
||
$code = $setting['module_code'];
|
||
if (isset($modules[$code])) {
|
||
if (!empty($setting['name'])) {
|
||
$modules[$code]['name'] = $setting['name'];
|
||
}
|
||
if (isset($setting['description']) && $setting['description'] !== '') {
|
||
$modules[$code]['description'] = $setting['description'];
|
||
}
|
||
if (isset($setting['price_monthly'])) {
|
||
$modules[$code]['price_monthly'] = (int) $setting['price_monthly'];
|
||
}
|
||
if (isset($setting['price_yearly'])) {
|
||
$modules[$code]['price_yearly'] = (int) $setting['price_yearly'];
|
||
}
|
||
if (isset($setting['trial_days'])) {
|
||
$modules[$code]['trial_days'] = (int) $setting['trial_days'];
|
||
}
|
||
}
|
||
}
|
||
|
||
return $modules;
|
||
}
|
||
|
||
/**
|
||
* Проверка активности модуля для организации
|
||
*/
|
||
public function isModuleActive(string $moduleCode, ?int $organizationId = null): bool
|
||
{
|
||
if ($moduleCode === 'base') {
|
||
return true;
|
||
}
|
||
|
||
$orgId = $organizationId ?? session()->get('active_org_id');
|
||
if (!$orgId) {
|
||
return false;
|
||
}
|
||
|
||
$subscription = $this->getSubscription($orgId, $moduleCode);
|
||
if (!$subscription) {
|
||
return false;
|
||
}
|
||
|
||
return $subscription['status'] === 'active';
|
||
}
|
||
|
||
/**
|
||
* Проверка доступности модуля (активен или в триале)
|
||
*/
|
||
public function isModuleAvailable(string $moduleCode, ?int $organizationId = null): bool
|
||
{
|
||
if ($moduleCode === 'base') {
|
||
return true;
|
||
}
|
||
|
||
$orgId = $organizationId ?? session()->get('active_org_id');
|
||
if (!$orgId) {
|
||
return false;
|
||
}
|
||
|
||
$subscription = $this->getSubscription($orgId, $moduleCode);
|
||
if (!$subscription) {
|
||
return false;
|
||
}
|
||
|
||
return in_array($subscription['status'], ['active', 'trial'], true);
|
||
}
|
||
|
||
/**
|
||
* Получение подписки организации на модуль
|
||
*/
|
||
public function getSubscription(int $organizationId, string $moduleCode): ?array
|
||
{
|
||
$builder = $this->db->table($this->subscriptionsTable);
|
||
return $builder->where('organization_id', $organizationId)
|
||
->where('module_code', $moduleCode)
|
||
->get()
|
||
->getRowArray();
|
||
}
|
||
|
||
/**
|
||
* Получение всех подписок организации
|
||
*/
|
||
public function getOrganizationSubscriptions(int $organizationId): array
|
||
{
|
||
$builder = $this->db->table($this->subscriptionsTable);
|
||
return $builder->where('organization_id', $organizationId)
|
||
->orderBy('created_at', 'DESC')
|
||
->get()
|
||
->getResultArray();
|
||
}
|
||
|
||
/**
|
||
* Получение всех активных модулей организации
|
||
*/
|
||
public function getActiveModules(int $organizationId): array
|
||
{
|
||
$builder = $this->db->table($this->subscriptionsTable);
|
||
$subscriptions = $builder->where('organization_id', $organizationId)
|
||
->whereIn('status', ['active', 'trial'])
|
||
->get()
|
||
->getResultArray();
|
||
|
||
$modules = array_column($subscriptions, 'module_code');
|
||
$modules[] = 'base';
|
||
|
||
return array_unique($modules);
|
||
}
|
||
|
||
/**
|
||
* Создание/обновление подписки
|
||
*/
|
||
public function upsertSubscription(
|
||
int $organizationId,
|
||
string $moduleCode,
|
||
string $status = 'active',
|
||
?int $days = null
|
||
): bool {
|
||
$existing = $this->getSubscription($organizationId, $moduleCode);
|
||
|
||
$data = [
|
||
'organization_id' => $organizationId,
|
||
'module_code' => $moduleCode,
|
||
'status' => $status,
|
||
'expires_at' => $days > 0 ? date('Y-m-d H:i:s', strtotime("+{$days} days")) : null,
|
||
'updated_at' => date('Y-m-d H:i:s'),
|
||
];
|
||
|
||
if ($existing) {
|
||
return $this->db->table($this->subscriptionsTable)
|
||
->where('id', $existing['id'])
|
||
->update($data);
|
||
}
|
||
|
||
$data['created_at'] = date('Y-m-d H:i:s');
|
||
return $this->db->table($this->subscriptionsTable)->insert($data);
|
||
}
|
||
|
||
/**
|
||
* Удаление подписки
|
||
*/
|
||
public function deleteSubscription(int $subscriptionId): bool
|
||
{
|
||
return $this->db->table($this->subscriptionsTable)
|
||
->where('id', $subscriptionId)
|
||
->delete();
|
||
}
|
||
|
||
/**
|
||
* Запуск триала для модуля
|
||
*/
|
||
public function startTrial(int $organizationId, string $moduleCode, int $trialDays = 14): bool
|
||
{
|
||
$config = $this->getModuleConfig($moduleCode);
|
||
if (!$config || $config['trial_days'] <= 0) {
|
||
return false;
|
||
}
|
||
|
||
return $this->upsertSubscription(
|
||
$organizationId,
|
||
$moduleCode,
|
||
'trial',
|
||
$trialDays
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Активация подписки
|
||
*/
|
||
public function activate(int $organizationId, string $moduleCode, int $months = 1): bool
|
||
{
|
||
return $this->upsertSubscription(
|
||
$organizationId,
|
||
$moduleCode,
|
||
'active',
|
||
$months * 30
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Отмена подписки
|
||
*/
|
||
public function cancel(int $organizationId, string $moduleCode): bool
|
||
{
|
||
$existing = $this->getSubscription($organizationId, $moduleCode);
|
||
if (!$existing) {
|
||
return false;
|
||
}
|
||
|
||
return $this->db->table($this->subscriptionsTable)
|
||
->where('id', $existing['id'])
|
||
->update(['status' => 'cancelled', 'updated_at' => date('Y-m-d H:i:s')]);
|
||
}
|
||
|
||
/**
|
||
* Получение всех подписок (для суперадмина)
|
||
*/
|
||
public function getAllSubscriptions(): array
|
||
{
|
||
$builder = $this->db->table($this->subscriptionsTable);
|
||
return $builder
|
||
->select('organization_subscriptions.*, organizations.name as organization_name')
|
||
->join('organizations', 'organizations.id = organization_subscriptions.organization_id')
|
||
->orderBy('organization_subscriptions.created_at', 'DESC')
|
||
->get()
|
||
->getResultArray();
|
||
}
|
||
|
||
/**
|
||
* Статистика по модулям (для суперадмина)
|
||
*/
|
||
public function getModuleStats(): array
|
||
{
|
||
$stats = [];
|
||
$modules = $this->getAllModules();
|
||
|
||
foreach ($modules as $code => $module) {
|
||
$activeCount = $this->db->table($this->subscriptionsTable)
|
||
->where('module_code', $code)
|
||
->where('status', 'active')
|
||
->countAllResults();
|
||
|
||
$trialCount = $this->db->table($this->subscriptionsTable)
|
||
->where('module_code', $code)
|
||
->where('status', 'trial')
|
||
->countAllResults();
|
||
|
||
$stats[$code] = [
|
||
'name' => $module['name'],
|
||
'active' => $activeCount,
|
||
'trial' => $trialCount,
|
||
];
|
||
}
|
||
|
||
return $stats;
|
||
}
|
||
|
||
/**
|
||
* Сохранение настроек модуля
|
||
*/
|
||
public function saveModuleSettings(
|
||
string $moduleCode,
|
||
?string $name = null,
|
||
?string $description = null,
|
||
?int $priceMonthly = null,
|
||
?int $priceYearly = null,
|
||
?int $trialDays = null
|
||
): bool {
|
||
$existing = $this->db->table($this->moduleSettingsTable)
|
||
->where('module_code', $moduleCode)
|
||
->get()
|
||
->getRowArray();
|
||
|
||
$data = [
|
||
'module_code' => $moduleCode,
|
||
'updated_at' => date('Y-m-d H:i:s'),
|
||
];
|
||
|
||
if ($name !== null) {
|
||
$data['name'] = $name;
|
||
}
|
||
if ($description !== null) {
|
||
$data['description'] = $description;
|
||
}
|
||
if ($priceMonthly !== null) {
|
||
$data['price_monthly'] = $priceMonthly;
|
||
}
|
||
if ($priceYearly !== null) {
|
||
$data['price_yearly'] = $priceYearly;
|
||
}
|
||
if ($trialDays !== null) {
|
||
$data['trial_days'] = $trialDays;
|
||
}
|
||
|
||
if ($existing) {
|
||
return $this->db->table($this->moduleSettingsTable)
|
||
->where('id', $existing['id'])
|
||
->update($data);
|
||
}
|
||
|
||
$data['created_at'] = date('Y-m-d H:i:s');
|
||
$data['is_active'] = 1;
|
||
return $this->db->table($this->moduleSettingsTable)->insert($data);
|
||
}
|
||
|
||
/**
|
||
* Получение настроек модуля
|
||
*/
|
||
public function getModuleSettings(string $moduleCode): ?array
|
||
{
|
||
return $this->db->table($this->moduleSettingsTable)
|
||
->where('module_code', $moduleCode)
|
||
->get()
|
||
->getRowArray();
|
||
}
|
||
}
|