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_PHONE' => $agent->user->phone,
'BROKER_INN' => $agent->company->inn,
'BROKER_CONTACT_ID' => $agent->bitrixId(),
'OBJECT_NAME' => Complex::find($this->client['complexId'])->name,
'CALLBACK_URL' => route('api.client', ['hash' => $deal->confirmToken]),
];

View File

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

View File

@ -45,6 +45,7 @@ public function syncDeals(Agent $agent)
foreach ($deals as $deal)
{
$inDeal = $deal['deal'];//входящие данные по сделке
if (count($deal['contacts']) == 0)
{
continue;
@ -70,7 +71,7 @@ public function syncDeals(Agent $agent)
{
$dealItem = Deal::create([
'client_id' => $client->id,
'complex_id' => $inDeal->complex_id,
'complex_id' => $inDeal['complex_id'],
'agent_id' => $agent->id,
'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::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')
@extends('layouts.admin')
@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-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\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
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\Model;
use Modules\Bitrix\Models\BitrixId;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
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()
{
if ($row = $this->bitrixable()->first())
if ($row = $this->bitrixy()->first())
{
return $row->bx_id;
}
@ -24,29 +28,16 @@ public function bitrixId()
}
public function setBitrixId($id): bool
{
$this->bitrixable()->delete();
$this->bitrixy()->delete();
$bitrixId = new BitrixId([
'bx_id' => $id
]);
if ($this->bitrixable()->save($bitrixId))
if ($this->bitrixy()->save($bitrixId))
{
return true;
}
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;
class ContractsController extends Controller
{
public function index(Request $request)
@ -32,11 +33,13 @@ public function index(Request $request)
public function delete(Contract $contract)
{
$deal = $contract->deal();
$deal = $contract->deal;
if ($contract->delete())
{
$bitrixable = $deal->bitrixy;
if ($deal->delete())
{
$bitrixable->delete();
return back()->with('success', 'Договор был успешно удален из базы данных');
}
}

View File

@ -74,7 +74,14 @@ private function appendFilter(&$query)
{
foreach ($this->filter as $fKey => $fValue)
{
$query->where($fKey, $fValue);
if (is_array($fValue))
{
$query->whereIn($fKey, $fValue);
}
else
{
$query->where($fKey, $fValue);
}
}
}
public function render()

View File

@ -36,14 +36,23 @@
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" style="">
<div class="mb-3">
<label for="statusFilter" class="form-label">Статус</label>
<select class="form-select" name="filter[status]" id="statusFilter">
@foreach ($statuses = GetContractStatuses() as $key => $status)
<option value="{{ $key }}">{{ $status }}</option>
@endforeach
</select>
<div class="form_label">
<label for="statusFilter" class="form-label">Статус</label>
<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)
<option @if (array_key_exists('status', $filter) && $filter['status'] == $key) checked @endif
value="{{ $key }}">{{ $status }}</option>
@endforeach
</select>
</div>
<span class="error_text"></span>
</div>
</div>
</div>
<div class="modal-footer">

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

@ -37,20 +37,20 @@ class="bi bi-arrow-right" viewBox="0 0 16 16">
</div>
<div class="col-3 text-end">
<!--<a href="" class="btn border-1 border-secondary-subtle text-secondary rounded-4 p-3">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor"
class="bi bi-trash3" viewBox="0 0 16 16">
<path
d="M6.5 1h3a.5.5 0 0 1 .5.5v1H6v-1a.5.5 0 0 1 .5-.5M11 2.5v-1A1.5 1.5 0 0 0 9.5 0h-3A1.5 1.5 0 0 0 5 1.5v1H1.5a.5.5 0 0 0 0 1h.538l.853 10.66A2 2 0 0 0 4.885 16h6.23a2 2 0 0 0 1.994-1.84l.853-10.66h.538a.5.5 0 0 0 0-1zm1.958 1-.846 10.58a1 1 0 0 1-.997.92h-6.23a1 1 0 0 1-.997-.92L3.042 3.5zm-7.487 1a.5.5 0 0 1 .528.47l.5 8.5a.5.5 0 0 1-.998.06L5 5.03a.5.5 0 0 1 .47-.53Zm5.058 0a.5.5 0 0 1 .47.53l-.5 8.5a.5.5 0 1 1-.998-.06l.5-8.5a.5.5 0 0 1 .528-.47M8 4.5a.5.5 0 0 1 .5.5v8.5a.5.5 0 0 1-1 0V5a.5.5 0 0 1 .5-.5" />
</svg>
</a>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor"
class="bi bi-trash3" viewBox="0 0 16 16">
<path
d="M6.5 1h3a.5.5 0 0 1 .5.5v1H6v-1a.5.5 0 0 1 .5-.5M11 2.5v-1A1.5 1.5 0 0 0 9.5 0h-3A1.5 1.5 0 0 0 5 1.5v1H1.5a.5.5 0 0 0 0 1h.538l.853 10.66A2 2 0 0 0 4.885 16h6.23a2 2 0 0 0 1.994-1.84l.853-10.66h.538a.5.5 0 0 0 0-1zm1.958 1-.846 10.58a1 1 0 0 1-.997.92h-6.23a1 1 0 0 1-.997-.92L3.042 3.5zm-7.487 1a.5.5 0 0 1 .528.47l.5 8.5a.5.5 0 0 1-.998.06L5 5.03a.5.5 0 0 1 .47-.53Zm5.058 0a.5.5 0 0 1 .47.53l-.5 8.5a.5.5 0 1 1-.998-.06l.5-8.5a.5.5 0 0 1 .528-.47M8 4.5a.5.5 0 0 1 .5.5v8.5a.5.5 0 0 1-1 0V5a.5.5 0 0 1 .5-.5" />
</svg>
</a>
<a href="" class="btn border-1 border-secondary-subtle text-secondary rounded-4 p-3">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor"
class="bi bi-pen" viewBox="0 0 16 16">
<path
d="m13.498.795.149-.149a1.207 1.207 0 1 1 1.707 1.708l-.149.148a1.5 1.5 0 0 1-.059 2.059L4.854 14.854a.5.5 0 0 1-.233.131l-4 1a.5.5 0 0 1-.606-.606l1-4a.5.5 0 0 1 .131-.232l9.642-9.642a.5.5 0 0 0-.642.056L6.854 4.854a.5.5 0 1 1-.708-.708L9.44.854A1.5 1.5 0 0 1 11.5.796a1.5 1.5 0 0 1 1.998-.001m-.644.766a.5.5 0 0 0-.707 0L1.95 11.756l-.764 3.057 3.057-.764L14.44 3.854a.5.5 0 0 0 0-.708z" />
</svg>
</a>-->
<a href="" class="btn border-1 border-secondary-subtle text-secondary rounded-4 p-3">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor"
class="bi bi-pen" viewBox="0 0 16 16">
<path
d="m13.498.795.149-.149a1.207 1.207 0 1 1 1.707 1.708l-.149.148a1.5 1.5 0 0 1-.059 2.059L4.854 14.854a.5.5 0 0 1-.233.131l-4 1a.5.5 0 0 1-.606-.606l1-4a.5.5 0 0 1 .131-.232l9.642-9.642a.5.5 0 0 0-.642.056L6.854 4.854a.5.5 0 1 1-.708-.708L9.44.854A1.5 1.5 0 0 1 11.5.796a1.5 1.5 0 0 1 1.998-.001m-.644.766a.5.5 0 0 0-.707 0L1.95 11.756l-.764 3.057 3.057-.764L14.44 3.854a.5.5 0 0 0 0-.708z" />
</svg>
</a>-->
</div>
</div>
<!--Основная часть-->

View File

@ -15,7 +15,7 @@
<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">
@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>
<body>
@ -127,7 +127,6 @@
</div>
</div>
@include('notice::index')
@vite(['resources/sass/app.scss', 'resources/js/app.js', 'resources/css/app.css', 'resources/css/docs.css'])
</body>
</html>

View File

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