bitrixy updated

This commit is contained in:
Thekindbull 2025-08-28 01:47:38 +08:00
parent 43568d03cc
commit 74da87ecea
19 changed files with 415 additions and 48 deletions

View File

@ -176,6 +176,7 @@ public function sendToBitrix(Deal $deal)
'BROKER_SECOND_NAME' => $agentName['secondName'], 'BROKER_SECOND_NAME' => $agentName['secondName'],
'BROKER_PHONE' => $agent->user->phone, 'BROKER_PHONE' => $agent->user->phone,
'BROKER_INN' => $agent->company->inn, 'BROKER_INN' => $agent->company->inn,
'BROKER_CONTACT_ID' => $agent->bitrixId(),
'OBJECT_NAME' => Complex::find($this->client['complexId'])->name, 'OBJECT_NAME' => Complex::find($this->client['complexId'])->name,
'CALLBACK_URL' => route('api.client', ['hash' => $deal->confirmToken]), 'CALLBACK_URL' => route('api.client', ['hash' => $deal->confirmToken]),
]; ];

View File

@ -42,7 +42,6 @@ public function contract()
protected static function booted(): void protected static function booted(): void
{ {
static::creating(function (Deal $deal) static::creating(function (Deal $deal)
{ {
$deal->confirm_token = hash('sha256', json_encode($deal->all())); $deal->confirm_token = hash('sha256', json_encode($deal->all()));
@ -58,11 +57,11 @@ protected static function booted(): void
static::deleted(function (Deal $deal) static::deleted(function (Deal $deal)
{ {
/*UserRole::where([ $deal->bitrixy()->delete();
'user_id' => $deal->client_id,
'role_id' => Role::CLIENT
])->delete();*/
}); });
} }
} }

View File

@ -45,6 +45,7 @@ public function syncDeals(Agent $agent)
foreach ($deals as $deal) foreach ($deals as $deal)
{ {
$inDeal = $deal['deal'];//входящие данные по сделке $inDeal = $deal['deal'];//входящие данные по сделке
if (count($deal['contacts']) == 0) if (count($deal['contacts']) == 0)
{ {
continue; continue;
@ -70,7 +71,7 @@ public function syncDeals(Agent $agent)
{ {
$dealItem = Deal::create([ $dealItem = Deal::create([
'client_id' => $client->id, 'client_id' => $client->id,
'complex_id' => $inDeal->complex_id, 'complex_id' => $inDeal['complex_id'],
'agent_id' => $agent->id, 'agent_id' => $agent->id,
'status' => DealStatus::UNIQUE, 'status' => DealStatus::UNIQUE,
]); ]);

View File

@ -0,0 +1,54 @@
<?php
namespace Modules\Admin\Http\Controllers;
use Modules\Docs\Models\Document;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Modules\Bitrix\Models\BitrixWebhooks;
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
]);
}
public function create(Request $request)
{
foreach ($this->names as $name)
{
if ($request->has($name) && $request->$name)
{
BitrixWebhooks::updateOrCreate(
['name' => $name],
[
'name' => $name,
'url' => $request->$name
]
);
}
}
return back();
}
public function delete($name)
{
BitrixWebhooks::where('name', $name)->delete();
}
}

View File

@ -61,4 +61,8 @@
Route::post('/admin/bitrix/agents/{agent}/set', [Modules\Admin\Http\Controllers\AdminBitrixController::class, 'setAgentId'])->name('admin.bitrix.agent.set'); Route::post('/admin/bitrix/agents/{agent}/set', [Modules\Admin\Http\Controllers\AdminBitrixController::class, 'setAgentId'])->name('admin.bitrix.agent.set');
Route::get('/admin/bitrix/agents/{agent}/deals/sync', [Modules\Admin\Http\Controllers\AdminBitrixController::class, 'syncDeals'])->name('admin.bitrix.agent.deals.sync'); Route::get('/admin/bitrix/agents/{agent}/deals/sync', [Modules\Admin\Http\Controllers\AdminBitrixController::class, 'syncDeals'])->name('admin.bitrix.agent.deals.sync');
Route::get('/admin/bitrix/webhooks', [Modules\Admin\Http\Controllers\AdminBitrixWebhooksController::class, 'index'])->name('admin.bitrix.webhooks');
Route::post('/admin/bitrix/webhooks', [Modules\Admin\Http\Controllers\AdminBitrixWebhooksController::class, 'create'])->name('admin.bitrix.webhooks.create');
}); });

View File

@ -1,6 +1,17 @@
@php($title = 'Битрикс24') @php($title = 'Битрикс24')
@extends('layouts.admin') @extends('layouts.admin')
@section('content') @section('content')
<div class="card mb-3">
<div class="card-header">
Вебхуки
</div>
<div class="card-body">
<h5 class="card-title">Управление вебхуками</h5>
<p class="card-text">Создать вебхуки на стороне Битрикса и указать их в этом разделе
</p>
<a href="{{ route('admin.bitrix.webhooks') }}" class="btn btn-primary">Перейти</a>
</div>
</div>
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
Агенты Агенты

View File

@ -0,0 +1,26 @@
@php($title = 'Битрикс24: вебхуки')
@extends('layouts.admin')
@section('content')
<div class="alert alert-primary" role="alert">
Создайтие вебхуки на методы, указанные ниже, на стороне вашего Битрикс24. Затем укажите эти вебхуки ниже.
</div>
<form action="{{ route('admin.bitrix.webhooks.create') }}" method="post">
@foreach ($names as $name)
<div class="row my-2">
@csrf
<div class="col-3">
{{ __('admin.' . $name) }}
</div>
<div class="col-9">
<input class="form-control" type="text" name="{{ $name }}"
value="{{ array_key_exists($name, $webhooks) ? $webhooks[$name]['url'] : '' }}">
</div>
</div>
@endforeach
<button type="submit" class="btn btn-primary mb-3">
<i class="bi bi-save"></i> Сохранить
</button>
</form>
@endsection

View File

@ -0,0 +1,36 @@
<?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::create('bx_webhooks', function (Blueprint $table)
{
$table->id();
$table->enum('name', [
'CREATE_COMPANY',
'CREATE_CONTACT',
'CREATE_DEAL',
'UPDATE_DEAL',
'DEAL_SYNC'
]);
$table->text('url');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('bx_webhooks');
}
};

View File

@ -5,6 +5,7 @@
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo; use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class BitrixId extends Model class BitrixId extends Model

View File

@ -0,0 +1,19 @@
<?php
namespace Modules\Bitrix\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;
class BitrixWebhooks extends Model
{
use HasFactory;
protected $table = 'bx_webhooks';
protected $fillable = [
'name',
'url'
];
}

View File

@ -6,17 +6,21 @@
use Illuminate\Database\Eloquent\Relations\MorphOne; use Illuminate\Database\Eloquent\Relations\MorphOne;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Modules\Bitrix\Models\BitrixId; use Modules\Bitrix\Models\BitrixId;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
trait Bitrixable trait Bitrixable
{ {
private function bitrixable(): MorphOne public function bitrixy(): MorphOne
{ {
return $this->morphOne(BitrixId::class, 'bitrixable'); return $this->MorphOne(BitrixId::class, 'bitrixable');
} }
public function bitrixId() public function bitrixId()
{ {
if ($row = $this->bitrixable()->first()) if ($row = $this->bitrixy()->first())
{ {
return $row->bx_id; return $row->bx_id;
} }
@ -24,29 +28,16 @@ public function bitrixId()
} }
public function setBitrixId($id): bool public function setBitrixId($id): bool
{ {
$this->bitrixable()->delete(); $this->bitrixy()->delete();
$bitrixId = new BitrixId([ $bitrixId = new BitrixId([
'bx_id' => $id 'bx_id' => $id
]); ]);
if ($this->bitrixable()->save($bitrixId)) if ($this->bitrixy()->save($bitrixId))
{ {
return true; return true;
} }
return false; return false;
} }
protected static function booted()
{
static::deleted(function ($bitrixableItem)
{
$this->bitrixable()->delete();
});
static::forceDeleted(function ($bitrixableItem)
{
$this->bitrixable()->delete();
});
}
} }

View File

@ -8,6 +8,7 @@
use Illuminate\Http\Request; use Illuminate\Http\Request;
class ContractsController extends Controller class ContractsController extends Controller
{ {
public function index(Request $request) public function index(Request $request)
@ -32,11 +33,13 @@ public function index(Request $request)
public function delete(Contract $contract) public function delete(Contract $contract)
{ {
$deal = $contract->deal(); $deal = $contract->deal;
if ($contract->delete()) if ($contract->delete())
{ {
$bitrixable = $deal->bitrixy;
if ($deal->delete()) if ($deal->delete())
{ {
$bitrixable->delete();
return back()->with('success', 'Договор был успешно удален из базы данных'); return back()->with('success', 'Договор был успешно удален из базы данных');
} }
} }

View File

@ -73,10 +73,17 @@ private function appendMode(&$query)
private function appendFilter(&$query) private function appendFilter(&$query)
{ {
foreach ($this->filter as $fKey => $fValue) foreach ($this->filter as $fKey => $fValue)
{
if (is_array($fValue))
{
$query->whereIn($fKey, $fValue);
}
else
{ {
$query->where($fKey, $fValue); $query->where($fKey, $fValue);
} }
} }
}
public function render() public function render()
{ {
return view('contracts::livewire.table.index', [ return view('contracts::livewire.table.index', [

View File

@ -36,15 +36,24 @@
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body" style=""> <div class="modal-body" style="">
<div class="mb-3"> <div class="mb-3">
<div class="form_label">
<label for="statusFilter" class="form-label">Статус</label> <label for="statusFilter" class="form-label">Статус</label>
<select class="form-select" name="filter[status]" id="statusFilter"> <div class="multiselect_block">
<label for="select-1" class="field_multiselect">Выберите нужные статусы</label>
<input id="checkbox-1" class="multiselect_checkbox" type="checkbox">
<label for="checkbox-1" class="multiselect_label"></label>
<select id="statusFilter" class="field_select form-select" name="filter[status][]"
multiple style="w-100">
@foreach ($statuses = GetContractStatuses() as $key => $status) @foreach ($statuses = GetContractStatuses() as $key => $status)
<option value="{{ $key }}">{{ $status }}</option> <option @if (array_key_exists('status', $filter) && $filter['status'] == $key) checked @endif
value="{{ $key }}">{{ $status }}</option>
@endforeach @endforeach
</select> </select>
</div> </div>
<span class="error_text"></span>
</div>
</div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>

View File

@ -0,0 +1,182 @@
.form_label {
position: relative;
min-height: 88px;
}
.form_text {
vertical-align: top;
display: block;
margin-bottom: 6px;
font-weight: 500;
font-size: 12px;
line-height: 16px;
letter-spacing: 0.04em;
color: #686ea1;
}
.form_text:after {
content: "*";
position: relative;
top: 0;
font-size: 13px;
color: #f00;
}
.form_label input,
.field_multiselect {
position: relative;
width: 100%;
display: block;
min-height: 46px;
border: 1px solid #cdd6f3;
box-sizing: border-box;
border-radius: 8px;
/*padding: 12px 40px 10px 16px;*/
font-size: 14px;
color: #a8acc9;
outline-color: #cdd6f3;
}
.form_label input::placeholder,
.field_multiselect::placeholder {
color: #a8acc9;
}
.form_label input:hover,
.field_multiselect:hover {
box-shadow: 0 0 2px rgba(0, 0, 0, 0.16);
}
.form_label input:focus,
.field_multiselect:focus {
box-shadow: 0 0 2px rgba(0, 0, 0, 0.16);
}
.field_multiselect_help {
position: absolute;
max-width: 100%;
background-color: #fff;
top: -48px;
left: 0;
opacity: 0;
}
.form_label input.error {
border-color: #eb5757;
}
.error_text {
color: #eb5757;
}
.field_multiselect {
padding-right: 60px;
}
.field_multiselect:after {
content: "";
position: absolute;
right: 14px;
top: 15px;
width: 6px;
height: 16px;
background: url("data:image/svg+xml,%3Csvg width='6' height='16' viewBox='0 0 6 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M3 0L6 5H0L3 0Z' fill='%23A8ACC9'/%3E%3Cpath d='M3 16L6 11H0L3 16Z' fill='%23A8ACC9'/%3E%3C/svg%3E") 50% 50% no-repeat;
}
.multiselect_block {
position: relative;
width: 100%;
}
.field_select {
position: absolute;
top: calc(100% - 2px);
left: 0;
width: 100%;
border: 2px solid #cdd6f3;
border-bottom-right-radius: 2px;
border-bottom-left-radius: 2px;
box-sizing: border-box;
outline-color: #cdd6f3;
z-index: 6;
}
.field_select[multiple] {
overflow-y: auto;
}
.field_select option {
display: block;
padding: 8px 16px;
cursor: pointer;
}
/*
.field_select option:checked {
background-color: #eceff3;
}
.field_select option:hover {
background-color: #d5e8fb;
}
*/
.field_multiselect button {
position: relative;
padding: 7px 34px 7px 8px;
}
.field_multiselect button:hover,
.field_multiselect button:focus {
background-color: #dbd1ee;
}
.field_multiselect button:after {
content: "";
position: absolute;
top: 10px;
right: 10px;
width: 16px;
height: 16px;
background: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M19.4958 6.49499C19.7691 6.22162 19.7691 5.7784 19.4958 5.50504C19.2224 5.23167 18.7792 5.23167 18.5058 5.50504L12.5008 11.5101L6.49576 5.50504C6.22239 5.23167 5.77917 5.23167 5.50581 5.50504C5.23244 5.7784 5.23244 6.22162 5.50581 6.49499L11.5108 12.5L5.50581 18.505C5.23244 18.7784 5.23244 19.2216 5.50581 19.495C5.77917 19.7684 6.22239 19.7684 6.49576 19.495L12.5008 13.49L18.5058 19.495C18.7792 19.7684 19.2224 19.7684 19.4958 19.495C19.7691 19.2216 19.7691 18.7784 19.4958 18.505L13.4907 12.5L19.4958 6.49499Z' fill='%234F5588'/%3E%3C/svg%3E") 50% 50% no-repeat;
background-size: contain;
}
.multiselect_label {
position: absolute;
top: 1px;
left: 2px;
width: 100%;
height: 44px;
cursor: pointer;
z-index: 3;
}
.field_select {
display: none;
}
input.multiselect_checkbox {
position: absolute;
right: 0;
top: 0;
width: 40px;
height: 40px;
border: none;
opacity: 0;
}
.multiselect_checkbox:checked~.field_select {
display: block;
}
.multiselect_checkbox:checked~.multiselect_label {
width: 40px;
left: initial;
right: 4px;
background: #ffffff url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M19.4958 6.49499C19.7691 6.22162 19.7691 5.7784 19.4958 5.50504C19.2224 5.23167 18.7792 5.23167 18.5058 5.50504L12.5008 11.5101L6.49576 5.50504C6.22239 5.23167 5.77917 5.23167 5.50581 5.50504C5.23244 5.7784 5.23244 6.22162 5.50581 6.49499L11.5108 12.5L5.50581 18.505C5.23244 18.7784 5.23244 19.2216 5.50581 19.495C5.77917 19.7684 6.22239 19.7684 6.49576 19.495L12.5008 13.49L18.5058 19.495C18.7792 19.7684 19.2224 19.7684 19.4958 19.495C19.7691 19.2216 19.7691 18.7784 19.4958 18.505L13.4907 12.5L19.4958 6.49499Z' fill='%234F5588'/%3E%3C/svg%3E") 50% 50% no-repeat;
}
.multiselect_checkbox:checked~.field_multiselect_help {
opacity: 1;
}

View File

@ -0,0 +1,22 @@
let multiselect_block = document.querySelectorAll(".multiselect_block");
multiselect_block.forEach(parent => {
let label = parent.querySelector(".field_multiselect");
let select = parent.querySelector(".field_select");
let text = label.innerHTML;
select.addEventListener("change", function (element) {
let selectedOptions = this.selectedOptions;
label.innerHTML = "";
for (let option of selectedOptions) {
let button = document.createElement("button");
button.type = "button";
button.className = "btn_multiselect btn btn-primary m-1";
button.textContent = option.text;
button.onclick = _ => {
option.selected = false;
button.remove();
if (!select.selectedOptions.length) label.innerHTML = text
};
label.append(button);
}
})
})

View File

@ -15,7 +15,7 @@
<link href="https://fonts.bunny.net/css?family=Nunito" rel="stylesheet"> <link href="https://fonts.bunny.net/css?family=Nunito" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
@vite(['resources/sass/app.scss', 'resources/js/app.js', 'resources/js/multiselect.js', 'resources/css/app.css', 'resources/css/docs.css', 'resources/css/multiselect.css'])
</head> </head>
<body> <body>
@ -127,7 +127,6 @@
</div> </div>
</div> </div>
@include('notice::index') @include('notice::index')
@vite(['resources/sass/app.scss', 'resources/js/app.js', 'resources/css/app.css', 'resources/css/docs.css'])
</body> </body>
</html> </html>

View File

@ -7,8 +7,10 @@ export default defineConfig({
input: [ input: [
'resources/sass/app.scss', 'resources/sass/app.scss',
'resources/js/app.js', 'resources/js/app.js',
'resources/js/multiselect.js',
'resources/css/app.css', 'resources/css/app.css',
'resources/css/docs.css', 'resources/css/docs.css',
'resources/css/multiselect.css',
], ],
refresh: true, refresh: true,
}), }),