- обновлена работа с администраированием агентств: сделано редактирование данных, а также управление агентами из админки (добавление, удаление)

- проведен рефакторинг компоненты создания агента
- в админку добавлена работа с url на стороне битрикса
- проведен рефакторинг кода по отправке данных в битрикс
- разнесены оставшиеся миграции по модулям
This commit is contained in:
Thekindbull 2025-11-11 15:29:04 +08:00
parent c6d63e87f0
commit b5b50159a7
46 changed files with 291 additions and 121 deletions

View File

@ -6,40 +6,34 @@
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Modules\Bitrix\Models\BitrixWebhooks;
use Modules\Bitrix\Enums\BitrixWebhooksEnum;
class AdminBitrixWebhooksController extends Controller
{
private $names = [
'CREATE_AGENT',
'CREATE_COMPANY',
'CREATE_CONTACT',
'CREATE_DEAL',
'UPDATE_DEAL',
'DEAL_SYNC'
];
public function index()
{
$webhooks = BitrixWebhooks::all()->keyBy('name')->toArray();
return view('admin::bitrix.webhooks', [
'names' => $this->names,
'webhooks' => $webhooks
'webhooksEnumCases' => BitrixWebhooksEnum::cases(),
'webhooks' => $webhooks
]);
}
public function create(Request $request)
{
foreach ($this->names as $name)
foreach (BitrixWebhooksEnum::cases() as $webhook)
{
if ($request->has($name) && $request->$name)
$webhookName = $webhook->name;
if ($request->has($webhookName) && $request->$webhookName)
{
BitrixWebhooks::updateOrCreate(
['name' => $name],
['name' => $webhookName],
[
'name' => $name,
'url' => $request->$name
'name' => $webhookName,
'url' => $request->$webhookName
]
);
}

View File

@ -6,6 +6,8 @@
use Illuminate\Http\Request;
use Modules\Main\Models\Company\Company;
use Modules\Main\Models\Company\CompanyStatus;
use Modules\Main\Models\Agent\Agent;
class AdminCompaniesController extends Controller
{
public function index(Request $request)
@ -26,14 +28,16 @@ public function index(Request $request)
public function edit(Company $company)
{
return view('admin::companies.edit', [
'company' => $company,
'company' => $company,
'companyAgentsRelation' => Agent::where('company_id', $company->id)->get(),
'companyAdminsRelation' => []
]);
}
public function update(Request $request, Company $company)
{
$company->update($request->only('name', 'email', 'phone'));
return to_route('admin.companies');
return to_route('admin.companies.edit', ['company' => $company]);
}
}

View File

@ -24,7 +24,7 @@
Route::get('/admin/companies', [Modules\Admin\Http\Controllers\AdminCompaniesController::class, 'index'])->name('admin.companies');
Route::get('/admin/companies/{company}/edit', [Modules\Admin\Http\Controllers\AdminCompaniesController::class, 'edit'])->name('admin.companies.edit');
Route::post('/admin/companies/{company}/update', [Modules\Admin\Http\Controllers\AdminUsersController::class, 'update'])->name('admin.companies.update');
Route::post('/admin/companies/{company}/update', [Modules\Admin\Http\Controllers\AdminCompaniesController::class, 'update'])->name('admin.companies.update');
Route::post('/admin/companies/{company}/delete', [Modules\Admin\Http\Controllers\AdminUsersController::class, 'update'])->name('admin.companies.delete');
Route::get('/admin/cities', [Modules\Admin\Http\Controllers\AdminCitiesController::class, 'index'])->name('admin.cities');

View File

@ -3,18 +3,19 @@
@extends('layouts.admin')
@section('content')
<div class="alert alert-primary" role="alert">
Создайтие вебхуки на методы, указанные ниже, на стороне вашего Битрикс24. Затем укажите эти вебхуки ниже.
Создайтие обработчики событий на стороне вашего Битрикс24 для указанных событий.
</div>
<form action="{{ route('admin.bitrix.webhooks.create') }}" method="post">
@foreach ($names as $name)
<div class="row my-2">
@foreach ($webhooksEnumCases as $webhookEnum)
<div class="row my-2 border-bottom">
@csrf
<div class="col-3">
{{ __('admin.' . $name) }}
<div class="col-3 vstack">
<span class="fw-bold">{{ $webhookEnum->name }}</span>
<small>{{ __('admin.' . $webhookEnum->name) . ' webhook label' }}</small>
</div>
<div class="col-9">
<input class="form-control" type="text" name="{{ $name }}"
value="{{ array_key_exists($name, $webhooks) ? $webhooks[$name]['url'] : '' }}">
<input class="form-control" type="text" name="{{ $webhookEnum->name }}"
value="{{ array_key_exists($webhookEnum->name, $webhooks) ? $webhooks[$webhookEnum->name]['url'] : '' }}">
</div>
</div>
@endforeach

View File

@ -1,9 +1,9 @@
@php($title = 'Агентства')
@php($title = $company->name)
@extends('layouts.admin')
@section('content')
<div class="fs-5 bg-light p-0 m-0 border border-1 rounded-4 p-3">
<h4 class="fw-bold my-3">Редактировать данные пользователя</h4>
<h4 class="fw-bold my-3">Редактировать данные агентства</h4>
<form action="{{ route('admin.companies.update', ['company' => $company]) }}" method="post"
enctype="multipart/form-data">
@csrf
@ -18,15 +18,15 @@
<div class="mb-3">
<label for="emailFormControl" class="form-label">Email</label>
<input type="text" class="form-control" id="emailFormControl" name="email"
value="{{ $user->email }}">
value="{{ $company->email }}">
@error('email')
<div class="text-danger">{{ $message }}</div>
@enderror
</div>
<div class="mb-3">
<label for="phoneFormControl" class="form-label">телефон</label>
<label for="phoneFormControl" class="form-label">Телефон</label>
<input type="text" class="form-control" id="phoneFormControl" name="phone"
value="{{ $user->phone }}">
value="{{ $company->phone }}">
@error('phone')
<div class="text-danger">{{ $message }}</div>
@enderror
@ -37,23 +37,33 @@
</div>
<div class="fs-5 bg-light p-0 m-0 border border-1 rounded-4 mt-3 pb-3">
<h4 class="fw-bold m-3">Роли пользователя</h4>
<div class="d-flex align-items-center m-3">
<h4 class="fw-bold">Агенты</h4>
<div class="ms-auto d-flex gap-3">
<a href="" class="btn btn-secondary">
<i class="bi bi-file-earmark-excel"></i> <span class="d-none d-lg-inline">Загрузить из Excel</span>
</a>
<a href="" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#createAgentModal">
<i class="bi bi-person-plus-fill"></i> <span class="d-none d-lg-inline">Добавить</span>
</a>
</div>
</div>
<table class="table m-0">
<thead>
<tr scope="col">
<th>Роль
<th>Когда назначена
<th>ФИО
<th>Когда назначен
<th>
</tr>
</thead>
<tbody class=" ">
@foreach ($userRoles as $userRole)
@foreach ($companyAgentsRelation as $companyAgent)
<tr scope="row">
<td class="align-middle">
{{ __($userRole->role->name) }}
{{ $companyAgent->user->name }}
</td>
<td class="align-middle">
{{ $userRole->created_at->diffForHumans() }}
{{ $companyAgent->created_at->diffForHumans() }}
</td>
<td class="text-end">
<div class="dropdown" style="">
@ -63,7 +73,7 @@
</button>
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
<form method="post"
action="{{ route('admin.users.role.delete', ['userRole' => $userRole]) }}">
action="{{ route('admin.users.role.delete', ['userRole' => 999]) }}">
@csrf
<button class="dropdown-item" type="submit">Удалить</button>
</form>
@ -75,4 +85,5 @@
</tbody>
</table>
</div>
@livewire('company.agent.create', ['containerId' => 'createAgentModal', 'companyId' => $company->id])
@endsection

View File

@ -0,0 +1,24 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
DB::statement('ALTER TABLE bx_webhooks MODIFY COLUMN name VARCHAR(255)');
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('bx_webhooks');
}
};

View File

@ -0,0 +1,13 @@
<?php
namespace Modules\Bitrix\Enums;
enum BitrixWebhooksEnum
{
case CREATE_AGENT;
case CREATE_COMPANY;
case CREATE_CONTACT;
case CREATE_DEAL;
case ADD_CLIENT_TO_DEAL;
case DEAL_SYNC;
case CREATE_REG_MANAGER;
}

View File

@ -3,17 +3,52 @@
namespace Modules\Bitrix\Models;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Modules\Bitrix\Enums\BitrixWebhooksEnum;
use Modules\Bitrix\Models\BitrixWebhooks;
class BitrixSender
{
protected $url;
protected $data;
public $resultData;
public function __construct(string $url, array $data)
public function __construct(array $data)
{
$this->url = $url;
$this->data = $data;
}
protected function getUrl()
{
$webhookEnumItem = false;
$childClass = get_class($this);
switch ( $childClass )
{
case SendAgent::class:
$webhookEnumItem = BitrixWebhooksEnum::CREATE_AGENT;
break;
case SendClient::class:
$webhookEnumItem = BitrixWebhooksEnum::CREATE_CONTACT;
break;
case SendCompany::class:
$webhookEnumItem = BitrixWebhooksEnum::CREATE_COMPANY;
break;
case SendDeal::class:
$webhookEnumItem = BitrixWebhooksEnum::CREATE_DEAL;
break;
case SendDealAgent::class:
$webhookEnumItem = BitrixWebhooksEnum::ADD_CLIENT_TO_DEAL;
break;
case SendDealClient::class:
$webhookEnumItem = BitrixWebhooksEnum::ADD_CLIENT_TO_DEAL;
break;
}
if ($webhookEnumItem)
{
return BitrixWebhooks::getUrlByName($webhookEnumItem);
}
else
{
throw new \Exception('Undefined url of webhook for ' . $childClass . ' class');
}
}
public function send()
{
$postdata = http_build_query(
@ -35,7 +70,7 @@ public function send()
try
{
$context = stream_context_create($opts);
$result = file_get_contents($this->url, false, $context);
$result = file_get_contents($this->getUrl(), false, $context);
$result = json_decode($result, $associative = true);
$this->resultData = $result;
if (is_array($this->resultData) && array_key_exists('result', $result))

View File

@ -4,16 +4,29 @@
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Modules\Bitrix\Enums\BitrixWebhooksEnum;
class BitrixWebhooks extends Model
{
use HasFactory;
protected $table = 'bx_webhooks';
protected $fillable = [
'name',
'url'
];
/**
* Get webhook url by its name
* @param string $name webhook name
* @return string|bool url of webhook or false
*/
public static function getUrlByName(BitrixWebhooksEnum $webhook): string|bool
{
$row = BitrixWebhooks::where('name', $webhook->name);
if ($row->count() == 1)
{
return $row->first()->url;
}
return false;
}
}

View File

@ -16,7 +16,5 @@ public function __construct(Agent $agent)
'name' => $agent->user->name,
'phones' => [$agent->user->phone],
];
dd($this->data);
$this->url = 'https://b24alfa.pro/channels/lk/createContact/';
}
}

View File

@ -16,6 +16,5 @@ public function __construct(Client $client)
'name' => $client->name,
'phones' => [$client->phone],
];
$this->url = 'https://b24alfa.pro/channels/lk/createContact/';
}
}

View File

@ -15,13 +15,12 @@ public function __construct(Company $company)
'name' => $company->name,
'inn' => $company->inn,
'email' => $company->email,
'phone' => $company->phone,
'address' => $company->legal_address,
'callbackUrl' => route('api.company.confirm', [
'token' => $company->secret
])
];
//dd($company);
$this->url = 'https://b24alfa.pro/channels/lk/createCompany/';
}
}

View File

@ -15,6 +15,5 @@ public function __construct(Deal $deal)
$this->data = [
'complexName' => $deal->complex->name
];
$this->url = 'https://b24alfa.pro/channels/lk/createSmartProcess/';
}
}

View File

@ -17,6 +17,5 @@ public function __construct(Deal $deal)
'contactId' => $deal->agent->bitrixId(),
'type' => 'agent'
];
$this->url = 'https://b24alfa.pro/channels/lk/addContactToSmartProcess/';
}
}

View File

@ -18,6 +18,5 @@ public function __construct(Deal $deal, Client $client)
'contactId' => $client->bitrixId(),
'type' => 'contact'
];
$this->url = 'https://b24alfa.pro/channels/lk/addContactToSmartProcess/';
}
}

View File

@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('companies', function (Blueprint $table)
{
$table->string('phone');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('companies', function (Blueprint $table)
{
//
});
}
};

View File

@ -29,9 +29,10 @@ public function index(Request $request)
}
return view('main::company.agents.table', [
'agents' => $agents->get(),
'status' => $request->status,
'cities' => City::all()
'agents' => $agents->get(),
'companyId' => $admin->company_id,
'status' => $request->status,
'cities' => City::all()
])->with('statuses', AgentStatus::class);
}
else

View File

@ -4,26 +4,33 @@
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Modules\Main\Models\Company\Company;
use Modules\Main\Models\Company\CompanyAdmin;
use Modules\Main\Models\Agent\Agent;
use App\Models\User;
use Modules\User\Models\User;
use App\Notifications\UserRegistered;
class CreateAgentController extends Controller
{
public function __invoke(Request $request)
public function __invoke(Request $request, Company $company)
{
//dd($request->sendToEmail);
$admin = CompanyAdmin::where('user_id', auth()->id());
if (!$admin->count())
if (!$company)
{
abort(404);
return;
$admin = CompanyAdmin::where('user_id', auth()->id());
if (!$admin->count())
{
abort(404);
return;
}
$admin = $admin->first();
$company = Company::find($admin->company_id);
}
if (!$company)
{
return back()->with('error', 'Не удалось установить принадлежность создаваемого агента');
}
$admin = $admin->first();
$user = User::where('email', $request->email)->orWhere('phone', $request->phone)->first();
if ($user)
{
@ -45,7 +52,7 @@ public function __invoke(Request $request)
Agent::where('user_id', $user->id)->delete(); //на случай, если где-то этот пользователь уже был агентом
$agent = Agent::create([
'user_id' => $user->id,
'company_id' => $admin->company_id
'company_id' => $company->id
]);
}

View File

@ -0,0 +1,28 @@
<?php
namespace Modules\Main\Http\Livewire;
use Livewire\Component;
use Modules\Main\Models\Agent\Agent;
use Modules\Main\Models\Company\Company;
use Modules\Main\Models\Company\CompanyAdmin;
class CreateAgentLivewire extends Component
{
public $companyId;
public $containerId;
public function mount($containerId = 'createAgentModal', $companyId)
{
$this->companyId = $companyId;
$this->containerId = $containerId;
}
public function render()
{
return view('main::agent.livewire.create', [
]);
}
}

View File

@ -21,6 +21,7 @@ class Company extends Model
'full_name',
'inn',
'email',
'phone',
'address',
'legal_address',
'details',

View File

@ -8,7 +8,7 @@
class ModuleServiceProvider extends ServiceProvider
{
protected String $moduleName = 'Main';
protected string $moduleName = 'Main';
public function register()
{
@ -27,38 +27,42 @@ public function boot()
protected function registerViews()
{
$moduleViewsPath = __DIR__.'/../Views';
$moduleViewsPath = __DIR__ . '/../Views';
$this->loadViewsFrom(
$moduleViewsPath, strtolower($this->moduleName)
$moduleViewsPath,
strtolower($this->moduleName)
);
}
protected function registerLivewireViews()
{
$moduleViewsPath = __DIR__.'/../Views/livewire';
$moduleViewsPath = __DIR__ . '/../Views/livewire';
$this->loadViewsFrom(
$moduleViewsPath, strtolower($this->moduleName)
$moduleViewsPath,
strtolower($this->moduleName)
);
}
protected function registerMigrations()
{
$this->loadMigrationsFrom(
app_path('Modules/'.$this->moduleName.'/Database/Migrations')
app_path('Modules/' . $this->moduleName . '/Database/Migrations')
);
}
protected function registerConfig()
{
$path = app_path('Modules/'.$this->moduleName.'/Config/config.php');
$path = app_path('Modules/' . $this->moduleName . '/Config/config.php');
$this->mergeConfigFrom(
$path, strtolower($this->moduleName)
$path,
strtolower($this->moduleName)
);
}
protected function registerLivewire()
{
//Livewire::component('<name>', \Modules\<NAME>\Http\Livewire\<NAME>::class);
Livewire::component('company.agent.create', \Modules\Main\Http\Livewire\CreateAgentLivewire::class);
}
protected function registerComponent()

View File

@ -25,7 +25,8 @@
Route::get('/company/details/{company?}', [Modules\Main\Http\Controllers\Company\DetailsController::class, 'index'])->name('company.details');
Route::post('/company/{company}/details/', [Modules\Main\Http\Controllers\Company\DetailsController::class, 'store'])->name('company.details.store');
Route::get('/agents/table', [Modules\Main\Http\Controllers\Company\AgentsTableController::class, 'index'])->name('company.agents.table');
Route::post('/company/agents/store/', Modules\Main\Http\Controllers\Company\CreateAgentController::class)->name('company.agents.store');
//Route::post('/company/agents/store/', Modules\Main\Http\Controllers\Company\CreateAgentController::class)->name('company.agents.store');
Route::post('/companies/{company?}/agents/store/', Modules\Main\Http\Controllers\Company\CreateAgentController::class)->name('company.agents.store');
Route::post('/company/agents/{agent}/password/reset/', Modules\Main\Http\Controllers\Company\ResetAgentPasswordController::class)->name('company.agent.password.reset');
Route::get('/company/agents/{agent}/delete', Modules\Main\Http\Controllers\Company\DeleteAgentController::class)->name('company.agents.delete');
Route::get('/company/agents/{agent}/restore', Modules\Main\Http\Controllers\Company\RestoreAgentController::class)->name('company.agents.restore');

View File

@ -0,0 +1,42 @@
<div>
<div class="modal fade" id="{{ $containerId }}" tabindex="-1" aria-labelledby="createAgentModalLabel"
aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<form class="modal-content" action="{{ route('company.agents.store', ['company' => $companyId]) }}"
method="post">
<div class="modal-header">
<h1 class="modal-title fs-5" id="exampleModalLabel">Новый агент</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
@csrf
<div class="mb-3">
<label for="agentName" class="form-label">Полное имя (ФИО)</label>
<input type="text" class="form-control" id="agentName" name="name">
</div>
<div class="mb-3">
<label for="agentEmail" class="form-label">Электронная почта</label>
<input type="text" class="form-control" id="agentEmail" name="email">
</div>
<div class="mb-3">
<label for="agentPhone" class="form-label">Телефон</label>
<input type="text" class="form-control" id="agentPhone" name="phone">
</div>
<div class="mb-3 form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="sendEmailSwitch"
name="sendToEmail" checked>
<label class="form-check-label" for="sendEmailSwitch">Отправить учетные данные на электронную
почту</label>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
<input type="submit" class="btn btn-primary" value="Добавить">
</div>
</form>
</div>
</div>
</div>

View File

@ -38,45 +38,5 @@
</div>
@endif
</div>
<!-- Modal -->
<div class="modal fade" id="createAgentModal" tabindex="-1" aria-labelledby="createAgentModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<form class="modal-content" action="{{ route('company.agents.store') }}" method="post">
<div class="modal-header">
<h1 class="modal-title fs-5" id="exampleModalLabel">Новый агент</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
@csrf
<div class="mb-3">
<label for="agentName" class="form-label">Полное имя (ФИО)</label>
<input type="text" class="form-control" id="agentName" name="name">
</div>
<div class="mb-3">
<label for="agentEmail" class="form-label">Электронная почта</label>
<input type="text" class="form-control" id="agentEmail" name="email">
</div>
<div class="mb-3">
<label for="agentPhone" class="form-label">Телефон</label>
<input type="text" class="form-control" id="agentPhone" name="phone">
</div>
<div class="mb-3 form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="sendEmailSwitch"
name="sendToEmail" checked>
<label class="form-check-label" for="sendEmailSwitch">Отправить учетные данные на электронную
почту</label>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
<input type="submit" class="btn btn-primary" value="Добавить">
</div>
</form>
</div>
</div>
@livewire('company.agent.create', ['containerId' => 'createAgentModal', 'companyId' => $companyId])
@endsection

View File

@ -4,18 +4,21 @@
<div class="container">
<div class="row justify-content-center">
<div class="text-success text-center">
<svg xmlns="http://www.w3.org/2000/svg" width="70" height="70" fill="currentColor" class="bi bi-check-circle"
viewBox="0 0 16 16">
<svg xmlns="http://www.w3.org/2000/svg" width="70" height="70" fill="currentColor"
class="bi bi-check-circle" viewBox="0 0 16 16">
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16" />
<path
d="m10.97 4.97-.02.022-3.473 4.425-2.093-2.094a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-1.071-1.05" />
</svg>
</div>
<div class="alert alert-success mt-3" role="alert">
Спасибо! Мы направили Вашу заявку на модерацию. Как только заявка на подключение будет обработана, мы направим
Спасибо! Мы направили Вашу заявку на модерацию. Как только заявка на подключение будет обработана, мы
направим
Вам на электронную почту дальнейшие инструкции!
</div>
<div class="d-flex justify-content-center">
<a href="route('welcome')" class="btn btn-primary">Вернуться к авторизации</a>
</div>
</div>
</div>
@endsection

View File

@ -93,6 +93,11 @@ class="bi bi-caret-left" viewBox="0 0 16 16">
@livewire('admin.menu')
</div>
<div class="col-10 px-0 px-md-4">
@if (session('success'))
<div class="alert alert-success">
{{ session('success') }}
</div>
@endif
@foreach ($errors->all() as $error)
{{ $error }}
@endforeach

View File

@ -18,7 +18,7 @@
Route::get('/', function ()
{
return view(view: 'welcome');
});
})->name('welcome');
Auth::routes();