обновлена модель сделок: добавлена возмжность нескольких клиентов. Обновлена форма создания клиентов

This commit is contained in:
Thekindbull 2025-11-09 15:45:07 +08:00
parent ea577e9a3b
commit a8056ce610
35 changed files with 1033 additions and 630 deletions

View File

@ -1,27 +0,0 @@
<?php
namespace App\Http\Controllers\Deal;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Modules\Contracts\Models\Contract;
class ContractController extends Controller
{
public function index(Contract $contract)
{
return view(
'clients.contract.index',
[
'contract' => $contract
]
);
}
public function getAllDealsInCompany(Request $request)
{
$user = auth()->user();
}
}

View File

@ -7,11 +7,14 @@
use Livewire\WithoutUrlPagination;
use App\Models\User;
use Modules\Main\Models\Deal\Client;
use Modules\Main\Models\Agent\Agent;
use Modules\Main\Models\Company\CompanyAdmin;
use App\Models\Deal\Deal;
use App\Models\Deal\DealStatus;
use App\Models\Deal\Client;
use Modules\Main\Models\Deal\Deal;
use Modules\Main\Models\Deal\DealStatus;
use Livewire\Attributes\On;
class ClientsTable extends Component
{
use WithPagination, WithoutUrlPagination;
@ -27,52 +30,48 @@ public function mount($status = null, $count = 10, $mode = 'full')
$this->mode = $mode;
}
#[On('clientCreated')]
public function getDeals()
{
$deals = false;
$user = auth()->user();
$clients = false;
$user = auth()->user();
if ($admin = CompanyAdmin::where('user_id', $user->id)->first())
{
/*$deals = Deal::whereIn('agent_id', function ($query) use ($admin)
$deals = Deal::whereIn('agent_id', function ($query) use ($admin)
{
$query->select('id');
$query->from('agents');
$query->where('company_id', $admin->company_id);
});*/
$agentsOfCompany = Agent::where('company_id', $admin->company_id)->get()->pluck('id');
$clients = Client::whereIn('id', function ($query) use ($agentsOfCompany)
{
$query->select('client_id')
->from('deals')
->whereIn('agent_id', $agentsOfCompany);
});
}
elseif ($agent = Agent::where('user_id', $user->id)->first())
{
//$deals = Deal::where('agent_id', $agent->id);
$deals = Deal::where('agent_id', $agent->id);
}
$clients = Client::whereIn('id', function ($query) use ($agent)
$deals = $deals->pluck('id');
$clients = Client::join('deal_clients', 'users.id', '=', 'deal_clients.client_id')
->whereIn('users.id', function ($query) use ($deals)
{
$query->select('client_id')
->from('deals')
->where('agent_id', $agent->id);
->from('deal_clients')
->whereIn('deal_id', $deals);
})
->orderBy('deal_clients.id', 'desc')
->with('deals');
;
});
}
$clients = $clients->orderBy('name');
return $clients;
}
public function render()
function getClients()
{
$clients = $this->getDeals();
if ($this->status && $this->status == DealStatus::UNIQUE)
{
/*$deals = $deals
->whereIn('status', [DealStatus::UNIQUE])
->orderBy('id', 'desc')->paginate($this->count, ['*'], 'unique_clients');*/
$clients = $clients->whereHas('deals', function ($query)
{
$query->where('status', DealStatus::UNIQUE);
@ -82,9 +81,6 @@ public function render()
}
elseif ($this->status && $this->status == DealStatus::NOT_UNIQUE)
{
/*$deals = $deals
->whereIn('status', [DealStatus::MODERATION, DealStatus::NEW , DealStatus::NOT_UNIQUE])
->orderBy('id', 'desc')->paginate($this->count, ['*'], 'not_unique_clients');*/
$clients = $clients->whereHas('deals', function ($query)
{
$query->whereIn('status', [DealStatus::MODERATION, DealStatus::NEW , DealStatus::NOT_UNIQUE]);
@ -95,22 +91,18 @@ public function render()
/*$deals = $deals->orderBy('id', 'desc')->paginate($this->count, ['*'], 'all_clients');*/
$clients = $clients->paginate($this->count, ['*'], 'all_clients');
}
return $clients;
}
public function render()
{
return view(
'livewire.clients-table',
[
'clients' => $clients,
'clients' => $this->getClients(),
'statuses' => DealStatus::class
]
);
}
function getClients()
{
/*$clients = User::whereIn('id', function ($query)
{
});*/
$clients = false;
//$clients = Client::ofAgent();
return $clients;
}
}

View File

@ -1,198 +0,0 @@
<?php
namespace App\Livewire;
use Livewire\Component;
use App\Models\User;
use App\Models\Deal\Deal;
use App\Models\Deal\DealStatus;
use App\Models\Complex;
use App\Models\Agent\Agent;
use App\Models\Bitrix\SendClient;
use App\Models\Company\CompanyAdmin;
class CreateClientForm extends Component
{
const NEW = 1;
const ERROR = 2;
const SUCCESS = 3;
const READY = 4;
public $client;
public $clientSecondary;
public $complexes;
public $status;
public $result;
public $agent;
protected $messages = [
'client.firstName.required' => 'Необходимо указать имя клиента',
'client.secondName.required' => 'Необходимо указать фамилию клиента',
'client.phone.required' => 'Необходимо указать телефон',
'client.phone.regex' => 'Телефон должен быть в международном формате',
'client.phone.unique' => 'Клиент с таким телефоном уже существует',
'agent.integer' => 'Необходимо указать агента, от которого добавляется контакт'
];
protected function rules()
{
return [
'agent' => ['required', 'integer'],
'client.firstName' => ['required', 'string', 'max:255'],
'client.secondName' => ['required', 'string', 'max:255'],
'client.phone' => ['required', 'string', 'regex:/^(\+7)([0-9]{3})([-]{1})([0-9]{3})([-]{1})([0-9]{4})/i']
//'client.phone' => ['required', 'string', 'regex:/^\+7 \d{3} \d{3}-\d{2}-\d{2}$/'],
//'client.phone' => ['required', 'string']
];
}
public function mount()
{
$agent = Agent::where('user_id', auth()->user()->id)->first();
$this->client = [
'firstName' => '',
'secondName' => '',
'phone' => '',
'complexId' => ''
];
$this->status = self::NEW;
if ($agent)
{
$this->agent = $agent->id;
$this->complexes = Complex::where('city_id', $agent->company->city_id)->get();
}
else
{
$this->agent = false;
$admin = CompanyAdmin::where('user_id', auth()->user()->id)->first();
if ($admin)
{
$this->complexes = Complex::where('city_id', $admin->company_id)->get();
}
}
$this->clientSecondary = false;
}
public function update()
{
}
public function updated($propertyName)
{
$this->status = self::NEW;
$this->validateOnly($propertyName);
}
public function getClientSecondary()
{
$this->clientSecondary = [
'firstName' => '',
'secondName' => '',
'phone' => '',
'complexId' => ''
];
}
public function deleteClientSecondary()
{
$this->clientSecondary = false;
}
public function render()
{
if (
$this->client['firstName']
&& $this->client['secondName']
&& $this->client['phone']
&& $this->client['complexId']
&& $this->status == self::NEW
)
{
$this->status = self::READY;
}
$data = [
'adminAccount' => false
];
if ($adminAccount = CompanyAdmin::where('user_id', auth()->user()->id)->first())
{
$data = [
'adminAccount' => $adminAccount,
'agents' => Agent::where('company_id', $adminAccount->company_id)->get()
];
}
return view(
'livewire.create-client-form',
$data
);
}
public function resetData()
{
$this->mount();
}
public function back()
{
$this->status = self::NEW;
}
public function save()
{
$validated = $this->validate($this->rules());
$newUser = User::updateOrCreate(
['phone' => $this->client['phone']],
[
'name' => trim($this->client['firstName'] . ' ' . $this->client['secondName']),
'phone' => $this->client['phone']
]
);
$data = [
'agent_id' => $this->agent
,
'client_id' => $newUser->id
,
'complex_id' => $this->client['complexId']
];
//$data['confirm_token'] = $this->client['confirmToken'] = hash('sha256', json_encode($data));
if ($newDeal = Deal::create($data))
{
if ($bitrixId = $this->sendToBitrix($newDeal))
{
$newDeal->bitrix_id = $bitrixId;
$newDeal->status = DealStatus::MODERATION;
$newDeal->save();
$this->result = $bitrixId;
return $this->status = self::SUCCESS;
}
else
{
$newDeal->delete();
}
}
return $this->status = self::ERROR;
}
public function sendToBitrix(Deal $deal)
{
$agent = $deal->agent;
$agentName = $agent->user->getPartialsName();
$data = [
'CLIENT_FIRST_NAME' => $this->client['firstName'],
'CLIENT_SECOND_NAME' => $this->client['secondName'],
'CLIENT_PHONE' => '+7' . $this->client['phone'],
'BROKER_FIRST_NAME' => $agentName['firstName'],
'BROKER_SECOND_NAME' => $agentName['secondName'],
'BROKER_PHONE' => $agent->user->phone,
'BROKER_INN' => $agent->company->inn,
//'BROKER_CONTACT' => $agent->bitrixId(),
'OBJECT_NAME' => Complex::find($this->client['complexId'])->name,
'CALLBACK_URL' => route('api.client', ['hash' => $deal->confirmToken]),
];
$sender = new SendClient($deal->id, $data);
if ($bitrixId = $agent->bitrixId())
{
$sender->setAgentId($bitrixId);
}
$response = $sender->send();
if ($response)
{
return $response;
}
else
{
return false;
}
}
}

View File

@ -1,72 +0,0 @@
<?php
namespace App\Models\Bitrix;
use Illuminate\Support\Facades\Http;
class SendClient
{
private $IBLOCK_TYPE_ID = 'lists';
//CONFIG для Альфы
private $URL = 'https://b24alfa.pro/rest/3165/v90a792nderzu0dj/lists.element.add.json';
private $IBLOCK_ID = 27;
private $ID;
const NAME = "NAME";
const CLIENT_SECOND_NAME = "PROPERTY_94";
const CLIENT_FIRST_NAME = "PROPERTY_95";
const CLIENT_PHONE = "PROPERTY_96";
const BROKER_SECOND_NAME = "PROPERTY_97";
const BROKER_FIRST_NAME = "PROPERTY_98";
const BROKER_PHONE = "PROPERTY_99";
const OBJECT_NAME = "PROPERTY_100";
const CALLBACK_URL = "PROPERTY_105";
const BROKER_CONTACT = "PROPERTY_108";
private $data = [];
public function __construct($id, $data)
{
$this->ID = env('BITRIX_CODE_PREFIX', '') . $id;
$data = array_change_key_case($data, CASE_UPPER);
$finalData = [];
$refl = new \ReflectionClass(__CLASS__);
$constants = $refl->getConstants();
foreach ($constants as $constName => $constValue)
{
foreach ($data as $key => $value)
{
if ($constName == $key)
{
$finalData[$constValue] = $value;
break;
}
}
}
$finalData['NAME'] = $finalData[self::CLIENT_FIRST_NAME] . ' ' . $finalData[self::CLIENT_SECOND_NAME];
$this->data = $finalData;
return;
}
public function setBitrixId($id)
{
$this->data['PROPERTY_123'] = $id;
}
public function setAgentId($id)
{
$this->data['PROPERTY_108'] = $id;
}
public function send()
{
$data = [
'IBLOCK_TYPE_ID' => $this->IBLOCK_TYPE_ID,
'IBLOCK_ID' => $this->IBLOCK_ID,
'ELEMENT_CODE' => $this->ID,
'FIELDS' => $this->data
];
$sender = new BitrixSender($this->URL, $data);
return $sender->send();
}
}

View File

@ -1,64 +0,0 @@
<?php
namespace App\Models\Bitrix;
use App\Models\Company\Company;
use App\Models\Bitrix\BitrixSender;
use Illuminate\Support\Facades\Http;
class SendCompany
{
private $IBLOCK_TYPE_ID = 'rest_entity';
//CONFIG для Альфы
private $URL = 'https://b24alfa.pro/rest/3165/v90a792nderzu0dj/lists.element.add.json';
private $IBLOCK_ID = 24;
private $ID;
const NAME = "NAME";
const INN = "PROPERTY_118";
const PHONE = "PROPERTY_121";
const EMAIL = "PROPERTY_120";
const LEGAL_ADDRESS = "PROPERTY_119";
const CALLBACK_URL = "PROPERTY_122";
private $data = [];
public function __construct($id, array $data)
{
$this->ID = env('BITRIX_CODE_PREFIX', '') . $id;
$data = array_change_key_case($data, CASE_UPPER);
$finalData = $this->castConstants($data);
$this->data = $finalData;
return;
}
private function castConstants($data)
{
$finalData = [];
$refl = new \ReflectionClass(__CLASS__);
$constants = $refl->getConstants();
foreach ($constants as $constName => $constValue)
{
foreach ($data as $key => $value)
{
if ($constName == $key)
{
$finalData[$constValue] = $value;
break;
}
}
}
return $finalData;
}
public function send()
{
$data = [
'IBLOCK_TYPE_ID' => $this->IBLOCK_TYPE_ID,
'IBLOCK_ID' => $this->IBLOCK_ID,
'ELEMENT_CODE' => $this->ID,
'FIELDS' => $this->data
];
$sender = new BitrixSender($this->URL, $data);
return $sender->send();
}
}

View File

@ -0,0 +1,32 @@
<?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::table('deals')->whereNotNull('client_id')->orderBy('id')->chunk(1000, function ($rows)
{
foreach ($rows as $row)
{
\DB::table('deal_clients')->updateOrInsert(['deal_id' => $row->id, 'client_id' => $row->client_id]);
// Or if you're creating new records in the new table based on the old, use insert:
// \DB::table('new_table_name')->insert(['id' => $row->id, 'field_name' => $row->field_name]);
}
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
}
};

View File

@ -0,0 +1,28 @@
<?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('deals', function (Blueprint $table)
{
$table->dropForeign(['client_id']);
$table->dropColumn(['client_id']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
}
};

View File

@ -7,6 +7,12 @@
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Modules\Main\Models\Deal\Deal;
use Modules\Main\Models\Deal\Client;
use Modules\Main\Models\Deal\DealClients;
use Modules\Main\Models\Agent\Agent;
use Modules\Main\Models\Company\Company;
use Modules\Main\Models\Complex;
class BitrixId extends Model
{
@ -20,4 +26,91 @@ public function bitrixable(): MorphTo
{
return $this->morphTo();
}
protected static function booted()
{
static::creating(function (BitrixId $bitrixId)
{
if ($bitrixId->bx_id)
{
return;
}
switch ( $bitrixId->bitrixable_type )
{
case Deal::class:
$deal = Deal::findOrFail($bitrixId->bitrixable_id);
$sender = new SendDeal($deal);
$result = $sender->send();
if ($result == true)
{
$id = $sender->resultData['id'];
$bitrixId->bx_id = $id;
return;
}
break;
case Agent::class:
$agent = Agent::findOrFail($bitrixId->bitrixable_id);
$sender = new SendAgent($agent);
$result = $sender->send();
if ($result == true)
{
$id = $sender->resultData['id'];
$bitrixId->bx_id = $id;
return;
};
break;
case DealClients::class:
$dealClient = DealClients::findOrFail($bitrixId->bitrixable_id);
$client = $dealClient->client;
$sender = new SendClient($client);
$result = $sender->send();
if ($result)
{
$id = $sender->resultData['id'];
$bitrixId->bx_id = $id;
$bitrixId->bitrixable_type = Client::class;
$bitrixId->bitrixable_id = $client->id;
return;
};
break;
case Company::class:
$company = Company::findOrFail($bitrixId->bitrixable_id);
$sender = new SendCompany($company);
$result = $sender->send();
if ($result == true)
{
$id = $sender->resultData['id'];
$bitrixId->bx_id = $id;
return;
};
break;
}
throw new \Exception('Error of bitrix identifier getter for ' . $bitrixId->bitrixable_type . ' with id ' . $bitrixId->bitrixable_id);
});
static::created(function (BitrixId $bitrixId)
{
if (!$bitrixId->bx_id)
{
return;
}
switch ( $bitrixId->bitrixable_type )
{
case Deal::class:
$deal = Deal::findOrFail($bitrixId->bitrixable_id);
$agentAppender = new SendDealAgent($deal);
$agentAppender->send();
break;
case Client::class:
$client = Client::findOrFail($bitrixId->bitrixable_id);
if ($dealClient = DealClients::where('client_id', $client->id)->orderByDesc('id')->first())
{
$clientAppender = new SendDealClient(Deal::find($dealClient->deal_id), $client);
$result = $clientAppender->send();
}
break;
}
});
}
}

View File

@ -1,58 +1,60 @@
<?php
namespace App\Models\Bitrix;
namespace Modules\Bitrix\Models;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
class BitrixSender
{
public $url;
public $data;
{
protected $url;
protected $data;
public $resultData;
public function __construct(string $url, array $data)
{
{
$this->url = $url;
$this->data = $data;
}
}
public function send()
{
{
$postdata = http_build_query(
$this->data
);
$opts = array(
'ssl' => array(
'verify_peer' => true,
'ssl' => array(
'verify_peer' => true,
'verify_peername' => true
),
'http' => array(
'method' => 'POST',
'header' =>
'method' => 'POST',
'header' =>
'Content-type: application/x-www-form-urlencoded' . "\r\n" .
'',
'content' => $postdata
)
);
try
{
{
$context = stream_context_create($opts);
$result = file_get_contents($this->url, false, $context);
$result = json_decode($result, $associative = true);
$this->resultData = $result;
if (array_key_exists('result', $result))
{
return $result['result'];
}
else
{
return false;
}
}
catch (\Exception $e)
{
Log::build([
'driver' => 'single',
'path' => storage_path('logs/bitrix.log'),
])->error($e->getMessage());
return false;
return $result['result'];
}
else
{
return false;
}
}
catch (\Exception $e)
{
Log::build([
'driver' => 'single',
'path' => storage_path('logs/bitrix.log'),
])->error($e->getMessage());
return false;
}
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace Modules\Bitrix\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Modules\Main\Models\Agent\Agent;
use Modules\Main\Models\Complex;
class SendAgent extends BitrixSender
{
use HasFactory;
public function __construct(Agent $agent)
{
$this->data = [
'name' => $agent->user->name,
'phones' => [$agent->user->phone],
];
dd($this->data);
$this->url = 'https://b24alfa.pro/channels/lk/createContact/';
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace Modules\Bitrix\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Modules\Main\Models\Deal\Client;
use Modules\Main\Models\Complex;
class SendClient extends BitrixSender
{
use HasFactory;
public function __construct(Client $client)
{
$this->data = [
'name' => $client->name,
'phones' => [$client->phone],
];
$this->url = 'https://b24alfa.pro/channels/lk/createContact/';
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace Modules\Bitrix\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Modules\Main\Models\Agent\Agent;
use Modules\Main\Models\Complex;
class SendCompany extends BitrixSender
{
use HasFactory;
public function __construct(Agent $agent)
{
$this->data = [
'name' => $agent->user->name,
'phones' => [$agent->user->phone],
];
$this->url = 'https://b24alfa.pro/channels/lk/createCompany/';
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace Modules\Bitrix\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Modules\Main\Models\Deal\Deal;
use Modules\Main\Models\Complex;
class SendDeal extends BitrixSender
{
use HasFactory;
public function __construct(Deal $deal)
{
$this->data = [
'complexName' => $deal->complex->name
];
$this->url = 'https://b24alfa.pro/channels/lk/createSmartProcess/';
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace Modules\Bitrix\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Modules\Main\Models\Deal\Deal;
use Modules\Main\Models\Agent\Agent;
class SendDealAgent extends BitrixSender
{
use HasFactory;
public function __construct(Deal $deal)
{
$this->data = [
'spId' => $deal->bitrixId(),
'contactId' => $deal->agent->bitrixId(),
'type' => 'agent'
];
$this->url = 'https://b24alfa.pro/channels/lk/addContactToSmartProcess/';
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace Modules\Bitrix\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Modules\Main\Models\Deal\Deal;
use Modules\Main\Models\Deal\Client;
class SendDealClient extends BitrixSender
{
use HasFactory;
public function __construct(Deal $deal, Client $client)
{
$this->data = [
'spId' => $deal->bitrixId(),
'contactId' => $client->bitrixId(),
'type' => 'contact'
];
$this->url = 'https://b24alfa.pro/channels/lk/addContactToSmartProcess/';
}
}

View File

@ -26,17 +26,48 @@ public function bitrixId()
}
return false;
}
public function setBitrixId($id): bool
public function setBitrixId($id = null)
{
$this->bitrixy()->delete();
$bitrixId = new BitrixId([
'bx_id' => $id
]);
if ($this->bitrixy()->save($bitrixId))
$bitrixy = $this->bitrixy;
if (!$bitrixy)
{
return true;
$bitrixId = new BitrixId();
$bitrixId->bx_id = $id ?? null;
$this->bitrixy()->save($bitrixId);
}
return false;
if ($id)
{
$bitrixy = $this->bitrixy;
$bitrixy->bx_id = $id;
$bitrixy->save();
}
return true;
}
protected static function create(array $attributes = [])
{
$model = new static();
$model->fill($attributes);
$model->save();
$model->setBitrixId();
return $model;
}
public static function firstOrCreate($attributes = [], $values = [])
{
$model = new static();
foreach ($attributes as $key => $value)
{
$model = $model->where($key, $value);
}
if ($model->count())
{
return $model->first();
}
else
{
self::create(array_merge($attributes, $values));
}
return true;
}
}

View File

@ -2,116 +2,165 @@
namespace Modules\ClientCreateForm\Http\Livewire;
use Exception;
use Laravel\Prompts\FormStep;
use Livewire\Component;
use Livewire\Attributes\Validate;
use Modules\Main\Models\Deal\Deal;
use Modules\Main\Models\Deal\Client;
use Modules\Main\Models\Deal\DealClients;
class ClientCreateLivewire extends Component
{
//public ClientCreateForm $form;
public $status;
public $availableAgents;
public $complexes;
public $maxClientsCount;
public $maxClientPhonesCount;
public $maxContactsCount;
public $maxContactPhonesCount;
/////////////////////
public $agent;
public $clients;
public $currentClientIndex;
public $currentClient;
public $addSecondaryClient;
public $secondaryClient;
public $clientLabels;
public $agentId;
public $contacts;
public $complexId;
public $currentContactIndex;
public $contactLabels;
public $bitrixId;
protected function rules()
{
return [
'contacts.*' => ['required', 'min:1'],
'contacts.*.firstName' => ['required', 'string', 'max:50'],
'contacts.*.secondName' => ['required', 'string', 'max:50'],
'contacts.*.phones.*' => 'required|string|regex:/^(\+7)([(]{1})([0-9]{3})([)]{1})([\s]{1})([0-9]{3})([-]{1})([0-9]{2})([-]{1})([0-9]{2})/',
'complexId' => ['required'],
'agentId' => ['required'],
];
}
protected function messages()
{
return [
'contacts.*.firstName.reqired' => 'Необходимо указать имя (отчество) клиента',
'contacts.*.secondName.reqired' => 'Необходимо указать фамилию клиента',
'contacts.*.firstName.max' => 'Указанное имя клиента слишком длинное',
'contacts.*.secondName.max' => 'Указанное имя клиента слишком длинное',
'contacts.*.phones.*.reqired' => 'Необходимо указать номер телефона клиента',
'contacts.*.phones.*.regex' => 'Указанный номер телефона некорректный',
];
}
public function mount()
{
$this->status = FormStatus::NEW;
$this->complexes = GetAvailableComplexes();
$this->availableAgents = GetAvailableAgents();
$this->maxClientsCount = 2;
$this->maxClientPhonesCount = 2;
$this->clientLabels = ['Основной контакт', 'Супруг/супруга'];
$this->status = FormStatus::NEW;
$this->secondaryClient = false;
$this->addSecondaryClient = true;
$this->maxContactsCount = 2;
$this->maxContactPhonesCount = 1;
$this->contactLabels = ['Основной контакт', 'Супруг/супруга'];
if (count($this->availableAgents) == 1) //чтобы не выводить в форму
{ //и не заставлять пользователя указывать вручную
$this->agent = $this->availableAgents[0]['id'];
$this->agentId = $this->availableAgents[0]['id'];
}
$this->addClient();
$this->addClient();//по-умолчанию сразу выводить супруга
$this->setCurrentClient(0);
$this->contacts = [];
$this->addContact();
$this->addContact();//по-умолчанию сразу выводить супруга
$this->setCurrentContactIndex(0);
}
public function updated($propertyName)
/**
* Метод срабатывает, когда пользователь нажимает в форме
* на кнопку "Добавить супруга"
* @return void
*/
public function addContact()
{
$this->status = FormStatus::NEW;
}
public function addClient()
{
if (!isset($this->clients))
if (!isset($this->contacts))
{
$this->clients = [];
$this->contacts = [];
}
if ($this->maxClientsCount > count($this->clients))
if ($this->maxContactsCount > count($this->contacts))
{
$this->clients[] = [
$this->contacts[] = [
'firstName' => '',
'secondName' => '',
'phones' => [''],
'complexId' => ''
'phones' => ['']
];
}
$this->setCurrentClient(count($this->clients) - 1);
$this->setCurrentContactIndex(count($this->contacts) - 1);
}
/**
* Метод срабатывает, когда пользователь нажимает кнопку "Удалить контакт"
* Кнопка доступна на всех вкладках, кроме первой
* @param mixed $index Индекс контакта по порядку следования на вкладках
* @return void
*/
public function deleteContact($index = false)
{
if ($index === false)
{
$index = $this->currentClientIndex;
$index = $this->currentContactIndex;
}
if ($index > 0)
{
unset($this->clients[$index]);
$this->clients = array_values($this->clients);
$this->setCurrentClient($index - 1);
unset($this->contacts[$index]);
$this->contacts = array_values($this->contacts);
$this->setCurrentContactIndex($index - 1);
}
}
public function setCurrentClient($index)
/**
* Вызвается при переходе пользователя по вкладкам
* @param mixed $index
* @return void
*/
public function setCurrentContactIndex($index)
{
$this->saveClient();
$this->updateCurrentClientIndex($index);
$this->currentContactIndex = $index;
$this->updated('currentContactIndex');
}
public function toggleSecondClient()
/**
* Вызывается, когда пользователь нажимает на кнопку
* "Добавить супруга" на первой вкладке
* @return void
*/
public function toggleSecondContact()
{
if ($this->currentClientIndex == 0)
if ($this->currentContactIndex == 0)
{
if (count($this->clients) == 2)
if (count($this->contacts) == 2)
{
$this->deleteContact(1);
}
elseif (count($this->clients) == 1)
elseif (count($this->contacts) == 1)
{
$this->addClient();
$this->addContact();
}
}
}
private function updateCurrentClientIndex($index)
/**
* Вызывается, когда пользователь нажимает на кнопку "добавить телефон"
* на вкладке любого из контактов
* @return void
*/
public function addPhoneForCurrentContact()
{
$this->currentClient = $this->clients[$index];
$this->currentClientIndex = $index;
}
public function saveClient()
{
if (isset($this->currentClientIndex))
if (count($this->contacts[$this->currentContactIndex]['phones']) < $this->maxContactPhonesCount)
{
//$this->form->validate();
$this->clients[$this->currentClientIndex] = $this->currentClient;
$this->contacts[$this->currentContactIndex]['phones'][] = '';
}
}
public function addPhoneForClient()
public function updated($propertyName)
{
if (count($this->currentClient['phones']) < $this->maxClientPhonesCount)
$this->status = FormStatus::IN_PROCESS;
$this->validateOnly($propertyName);
try
{
$this->currentClient['phones'][] = '';
$this->validate();
$this->status = FormStatus::READY;
}
catch (\Exception $e)
{
$this->status = FormStatus::IN_PROCESS;
}
}
public function render()
@ -123,6 +172,7 @@ public function render()
public function rendered()
{
$this->dispatch('phoneInputAdded');
}
public function resetData()
{
@ -130,75 +180,50 @@ public function resetData()
}
public function back()
{
$this->status = FormStatus::NEW;
$this->status = FormStatus::IN_PROCESS;
}
//далее - сохранение клиента в базу и отправка в битрикс.
//надо разделить на invoke классы!
/*public function save()
public function save()
{
$validated = $this->form->validate();
$newUser = User::updateOrCreate(
['phone' => $this->client['phone']],
[
'name' => trim($this->client['firstName'] . ' ' . $this->client['secondName']),
'phone' => $this->client['phone']
]
);
$data = [
'agent_id' => $this->agent
,
'client_id' => $newUser->id
,
'complex_id' => $this->client['complexId']
];
if (
!$deal = Deal::create([
'agent_id' => $this->agentId,
'complex_id' => $this->complexId
])
)
{
$this->status = FormStatus::ERROR;
return;
}
if ($newDeal = Deal::create($data))
foreach ($this->contacts as $contact)
{
if ($bitrixId = $this->sendToBitrix($newDeal))
if (
!$newUser = Client::updateOrCreate(
['phone' => $contact['phones'][0]],
[
'name' => trim($contact['firstName'] . ' ' . $contact['secondName']),
'phone' => $contact['phones'][0]
]
)
)
{
$newDeal->bitrix_id = $bitrixId;
$newDeal->status = DealStatus::MODERATION;
$newDeal->save();
$this->bitrixId = $bitrixId;
return $this->status = FormStatus::SUCCESS;
$this->status = FormStatus::ERROR;
return;
}
else
if (
!$dealClient = DealClients::firstOrCreate([
'deal_id' => $deal->id,
'client_id' => $newUser->id
])
)
{
$newDeal->delete();
$this->status = FormStatus::ERROR;
return;
}
}
return $this->status = FormStatus::ERROR;
$this->status = FormStatus::SUCCESS;
$this->dispatch('clientCreated');
}
public function sendToBitrix(Deal $deal)
{
$agent = $deal->agent;
$agentName = $agent->user->getPartialsName();
$data = [
'CLIENT_FIRST_NAME' => $this->client['firstName'],
'CLIENT_SECOND_NAME' => $this->client['secondName'],
'CLIENT_PHONE' => '+7' . $this->client['phone'],
'BROKER_FIRST_NAME' => $agentName['firstName'],
'BROKER_SECOND_NAME' => $agentName['secondName'],
'BROKER_PHONE' => $agent->user->phone,
'BROKER_INN' => $agent->company->inn,
//'BROKER_CONTACT' => $agent->bitrixId(),
'OBJECT_NAME' => Complex::find($this->client['complexId'])->name,
'CALLBACK_URL' => route('api.client', ['hash' => $deal->confirmToken]),
];
$sender = new SendClient($deal->id, $data);
if ($bitrixId = $agent->bitrixId())
{
$sender->setAgentId($bitrixId);
}
$response = $sender->send();
if ($response)
{
return $response;
}
else
{
return false;
}
}*/
}

View File

@ -8,27 +8,27 @@
</div>
</div>
<div class="w-100" wire:loading.remove wire:target="save, resetData, back">
@if ($status == FormStatus::NEW || $status == FormStatus::READY)
@if ($status == FormStatus::NEW || $status == FormStatus::IN_PROCESS || $status == FormStatus::READY)
<ul class="nav nav-tabs d-flex align-items-end border-0 pb-0">
@foreach ($clients as $clientIndex => $clientItem)
@foreach ($contacts as $contactIndex => $contactItem)
<li
class="nav-item overflow-hidden @if ($clientIndex == $currentClientIndex) rounded-top border-top border-end border-start bg-light @else m-1 bg-secondary-subtle rounded rouded-5 @endif">
<a class="nav-link me-1 border-0 @if ($clientIndex == $currentClientIndex) active text-primary @else py-1 text-dark @endif"
aria-current="page" href="#" wire:click="setCurrentClient({{ $clientIndex }})">
class="nav-item overflow-hidden @if ($contactIndex == $currentContactIndex) rounded-top border-top border-end border-start bg-light @else m-1 bg-secondary-subtle rounded rouded-5 @endif">
<a class="nav-link me-1 border-0 @if ($contactIndex == $currentContactIndex) active text-primary @else py-1 text-dark @endif"
aria-current="page" href="#" wire:click="setCurrentContactIndex({{ $contactIndex }})">
<i class="bi bi-person-standing"></i>
<span class="d-none d-xl-inline">
@if (array_key_exists($clientIndex, $clientLabels))
{{ $clientLabels[$clientIndex] }}
@if (array_key_exists($contactIndex, $contactLabels))
{{ $contactLabels[$contactIndex] }}
@else
Контакт {{ $clientIndex + 1 }}
Контакт {{ $contactIndex + 1 }}
@endif
</span>
</a>
</li>
@endforeach
@if ($maxClientsCount > count($clients))
@if ($maxContactsCount > count($contacts))
<li class="align-middle ps-2 mb-2">
<a class="text-light " href="#" wire:click="addClient">
<a class="text-light " href="#" wire:click="addContact">
<svg xmlns="http://www.w3.org/2000/svg" width="25" height="25" fill="currentColor"
class="bi bi-person-fill-add" viewBox="0 0 16 16">
<path
@ -45,13 +45,13 @@ class="bi bi-person-fill-add" viewBox="0 0 16 16">
<div class="row mx-2 g-2">
<div class="col-12 col-xl-6">
<div class="form-floating mb-3 m-xl-0">
<input wire:model.live="currentClient.firstName" id="currentClient.firstName"
type="text"
class="form-control rounded-4 @error('currentClient.firstName') is-invalid @enderror"
name="currentClient.firstName" required autocomplete="currentClient.firstName"
placeholder="Имя">
<label for="currentClient.firstName">Имя</label>
@error('currentClient.firstName')
<input wire:model.live="contacts.{{ $currentContactIndex }}.firstName"
id="contacts.{{ $currentContactIndex }}.firstName" type="text"
class="form-control rounded-4 @error('contacts.' . $currentContactIndex . '.firstName') is-invalid @enderror"
name="contacts.{{ $currentContactIndex }}.firstName" required
autocomplete="contacts.{{ $currentContactIndex }}.firstName" placeholder="Имя">
<label for="contacts.{{ $currentContactIndex }}.firstName">Имя</label>
@error('contacts.' . $currentContactIndex . '.firstName')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@ -60,13 +60,14 @@ class="form-control rounded-4 @error('currentClient.firstName') is-invalid @ende
</div>
<div class="col-12 col-xl-6">
<div class="form-floating mb-3 m-xl-0">
<input wire:model.live="currentClient.secondName" id="currentClient.secondName"
type="text"
class="form-control rounded-4 @error('currentClient.secondName') is-invalid @enderror"
name="currentClient.secondName" required autocomplete="currentClient.secondName"
<input wire:model.live="contacts.{{ $currentContactIndex }}.secondName"
id="contacts.{{ $currentContactIndex }}.secondName" type="text"
class="form-control rounded-4 @error('contacts.' . $currentContactIndex . '.secondName') is-invalid @enderror"
name="contacts.{{ $currentContactIndex }}.secondName" required
autocomplete="contacts.{{ $currentContactIndex }}.secondName"
placeholder="Фамилия">
<label for="currentClient.secondName">Фамилия</label>
@error('currentClient.secondName')
<label for="contacts.{{ $currentContactIndex }}.secondName">Фамилия</label>
@error('contacts.' . $currentContactIndex . '.secondName')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@ -74,24 +75,28 @@ class="form-control rounded-4 @error('currentClient.secondName') is-invalid @end
</div>
</div>
@foreach ($currentClient['phones'] as $phoneKey => $phone)
@foreach ($contacts[$currentContactIndex]['phones'] as $phoneKey => $phone)
<div class="col-12 hstack gap-2">
<div class="form-floating me-auto w-100">
<input wire:model="currentClient.phone.{{ $phoneKey }}"
id="currentClient.phone.{{ $phoneKey }}" type="tel" data-phone-pattern
class="form-control rounded-4 @error('currentClient.phone.{{ $phoneKey }}') is-invalid @enderror"
name="currentClient.phone.{{ $phoneKey }}" required autocomplete="phone"
placeholder="Телефон клиента">
<label for="currentClient.phone.{{ $phoneKey }}">Телефон
{{ count($currentClient['phones']) > 1 ? ' ' . ($phoneKey + 1) : '' }}</label>
@error('currentClient.phone.{{ $phoneKey }}')
<span class="invalid-feedback" role="alert">
<input
wire:model.live="contacts.{{ $currentContactIndex }}.phones.{{ $phoneKey }}"
id="contacts.{{ $currentContactIndex }}.phones.{{ $phoneKey }}"
type="text" data-phone-pattern
class="form-control rounded-4 @error('contacts.' . $currentContactIndex . '.phones.' . $phoneKey) is-invalid @enderror"
name="contacts.{{ $currentContactIndex }}.phones.{{ $phoneKey }}" required
autocomplete="phone" placeholder="Телефон клиента">
<label
for="contacts.{{ $currentContactIndex }}.phones.{{ $phoneKey }}">Телефон
{{ count($contacts[$currentContactIndex]['phones']) > 1 ? ' ' . ($phoneKey + 1) : '' }}</label>
@error('contacts.' . $currentContactIndex . '.phones.' . $phoneKey)
<span class="invalid-feedback d-block" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
@if (count($currentClient['phones']) == $phoneKey + 1 &&
count($this->currentClient['phones']) < $this->maxClientPhonesCount)
@if (count($contacts[$currentContactIndex]['phones']) == $phoneKey + 1 &&
count($contacts[$currentContactIndex]['phones']) < $this->maxContactPhonesCount)
<a class="" href="#" wire:click="addPhoneForClient"
title="Добавить еще один телефон для клиента">
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30"
@ -106,16 +111,16 @@ class="form-control rounded-4 @error('currentClient.phone.{{ $phoneKey }}') is-i
</div>
@endforeach
<div class="col-12">
@if ($currentClientIndex == 0 && count($clients) <= 2)
@if ($currentContactIndex == 0 && count($contacts) <= 2)
<div class="form-check form-switch">
<input class="form-check-input" wire:click="toggleSecondClient" type="checkbox"
<input class="form-check-input" wire:click="toggleSecondContact" type="checkbox"
role="switch" id="switchCheckChecked"
@if (count($clients) == 2) checked @endif>
@if (count($contacts) == 2) checked @endif>
<label class="form-check-label" for="switchCheckChecked">Добавить контакт
супруга/супруги</label>
</div>
@endif
@if ($currentClientIndex > 0)
@if ($currentContactIndex > 0)
<div class="text-end">
<a wire:click="deleteContact" class="text-primary"
style="text-decoration: underline dotted;" href="#">
@ -125,12 +130,12 @@ class="form-control rounded-4 @error('currentClient.phone.{{ $phoneKey }}') is-i
</div>
</div>
</div>
</div>
<div class="form-floating mb-3">
<select wire:model.live="client.complexId"
class="form-select rounded-4 @error('client.complexId') is-invalid @enderror" id="client.complexId"
name="client.complexId" aria-label="Жилой комплекс">
<select wire:model.live="complexId"
class="form-select rounded-4 @error('complexId') is-invalid @enderror" id="complexId"
name="complexId" aria-label="Жилой комплекс">
<option selected></option>
@foreach ($complexes as $complex)
<option value="{{ $complex['id'] }}">
@ -138,18 +143,19 @@ class="form-select rounded-4 @error('client.complexId') is-invalid @enderror" id
</option>
@endforeach
</select>
<label for="client.complexId">Жилой комплекс</label>
@error('client.complexId')
<label for="complexId">Жилой комплекс</label>
@error('complexId')
<span class="invalid-feedback " role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
@if (count($availableAgents) > 1)
<div class="form-floating mb-3">
<select wire:model.live="agent" class="form-select rounded-4 @error('agent') is-invalid @enderror"
id="agent" name="agent" aria-label="Агент">
<select wire:model.live="agentId"
class="form-select rounded-4 @error('agentId') is-invalid @enderror" id="agentId"
name="agent" aria-label="Агент">
<option selected></option>
@foreach ($availableAgents as $agent)
<option value="{{ $agent['id'] }}">
@ -157,8 +163,8 @@ class="form-select rounded-4 @error('client.complexId') is-invalid @enderror" id
</option>
@endforeach
</select>
<label for="client.complexId">Агент</label>
@error('agent')
<label for="agentId">Агент</label>
@error('agentId')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@ -166,16 +172,16 @@ class="form-select rounded-4 @error('client.complexId') is-invalid @enderror" id
</div>
@endif
@if ($status == FormStatus::READY && !$errors->any())
@if ($status != FormStatus::READY)
<div class="">
<button wire:click="save" class="btn btn-secondary rounded-4 fw-bold fs-5 w-100 py-3"
<button disabled class="btn btn-secondary disabled rounded-4 fw-bold fs-5 w-100 py-3"
style="background-color: #20184d;">
Создать нового клиента
</button>
</div>
@else
<div class="">
<button disabled class="btn btn-secondary disabled rounded-4 fw-bold fs-5 w-100 py-3"
<button wire:click="save" class="btn btn-secondary rounded-4 fw-bold fs-5 w-100 py-3"
style="background-color: #20184d;">
Создать нового клиента
</button>
@ -197,7 +203,7 @@ class="bi bi-check-circle-fill" viewBox="0 0 16 16">
<div class="text-center fs-5 ">Мы проверим его уникальность и направим Вам информацию в личном
кабинете</div>
<div class="text-center mt-3">
<button wire:click="$refresh" class="btn active border-white border-3 rounded">Продолжить</button>
<button wire:click="resetData" class="btn active border-white border-3 rounded">Добавить еще</button>
</div>
@endif

View File

@ -29,7 +29,15 @@ public function index(Request $request)
'filter' => $filter
]);
}
public function show(Contract $contract)
{
return view(
'contracts::contract',
[
'contract' => $contract
]
);
}
public function delete(Contract $contract)
{
$deal = $contract->deal;

View File

@ -49,6 +49,16 @@ private function getContracts()
return $contracts->get();
}
public function render()
{
return view('contracts::livewire.table.index', [
'contracts' => $this->getContracts(),
'statuses' => ContractStatus::class
]);
}
/* ДАЛЕЕ - методы для применения всяких фильтров */
private function appendMode(&$query)
{
if ($this->mode == 'active')
@ -87,11 +97,5 @@ private function appendFilter(&$query)
}
}
}
public function render()
{
return view('contracts::livewire.table.index', [
'contracts' => $this->getContracts(),
'statuses' => ContractStatus::class
]);
}
}

View File

@ -5,7 +5,7 @@
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use App\Models\Deal\Deal;
use Modules\Main\Models\Deal\Deal;
class Contract extends Model
{
use HasFactory;
@ -20,7 +20,6 @@ class Contract extends Model
'reward',
'square',
'floor',
'room',//Номер квартиры
'date',//дата ДДУ
'reg_date',//Дата регистрации ДДУ

View File

@ -8,6 +8,6 @@
Route::any('/contracts', [ContractsController::class, 'index'])->name('contracts');
Route::post('/contracts/{contract}/delete', [ContractsController::class, 'delete'])->name('contract.delete');
Route::post('/contracts/{contract}/comment', [ContractsController::class, 'comment'])->name('contract.comment');
Route::get('/contract/{contract}', [ContractsController::class, 'show'])->name('contract');
});

View File

@ -0,0 +1,239 @@
@php($title = 'Договор')
@extends('layouts.app')
@section('content')
<div class="row">
<div class="col-12 col-lg-8">
<!--Имя и контакты-->
@foreach ($contract->deal->clients as $client)
<div class="row py-5">
<div class="col">
<div class="fs-6 text-secondary">ФИО</div>
<div class="fw-bold fs-5 text-truncate">{{ $client->name }}</div>
</div>
<div class="col">
<div class="fs-6 text-secondary">Телефон</div>
<div class="fw-bold fs-5 text-truncate">{{ $client->phone }}</div>
</div>
@if ($client->email)
<div class="col">
<div class="fs-6 text-secondary">Email</div>
<div class="fw-bold fs-5 text-truncate">{{ $client->email }}</div>
</div>
@endif
<div class="d-none col 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-arrow-right" viewBox="0 0 16 16">
<path fill-rule="evenodd"
d="M1 8a.5.5 0 0 1 .5-.5h11.793l-3.147-3.146a.5.5 0 0 1 .708-.708l4 4a.5.5 0 0 1 0 .708l-4 4a.5.5 0 0 1-.708-.708L13.293 8.5H1.5A.5.5 0 0 1 1 8" />
</svg>
</a>
</div>
</div>
@endforeach
<!--Город-->
<div class="row bg-white py-3">
<div class="col-9">
<div class="fs-5 fw-bold my-2">{{ $contract->deal->complex->city->name }}</div>
<div class="fs-5 fw-bold text-secondary my-2">{{ $contract->deal->complex->name }}</div>
</div>
<div class="col-3 text-end">
</div>
</div>
<!--Основная часть-->
<div class="row">
<div class="col col-lg-8">
<div class="row my-4">
<div class="col col-md-6">
<div class="fs-6 text-secondary">Статус</div>
<div class="fw-bold fs-5 text-truncate text-dark-emphasis">
{{ __('contracts.status_' . $contract->status) }}
</div>
</div>
<div class="col col-md-6">
<div class="fs-6 text-secondary">Дата обновления</div>
<div class="fw-bold fs-5 text-truncate text-dark-emphasis">
{{ $contract->updated_at->format('d.m.y H:i') }}</div>
</div>
</div>
<div class="row my-4">
@if ($contract->square)
<div class="col col-md-4">
<div class="fs-6 text-secondary">Площадь объекта</div>
<div class="fw-bold fs-5 text-truncate text-dark-emphasis">{{ $contract->square }}</div>
</div>
@endif
@if ($contract->floor)
<div class="col col-md-4">
<div class="fs-6 text-secondary">Этаж</div>
<div class="fw-bold fs-5 text-truncate text-dark-emphasis">{{ $contract->floor }}</div>
</div>
@endif
@if ($contract->room)
<div class="col col-md-4">
<div class="fs-6 text-secondary">Помещение</div>
<div class="fw-bold fs-5 text-truncate text-dark-emphasis">{{ $contract->room }}</div>
</div>
@endif
</div>
<div class="row my-4">
<div class="col col-md-6">
<div class="fs-6 text-secondary">Стоимость</div>
<div class="fw-bold fs-5 text-truncate text-dark-emphasis">
{{ number_format($contract->price, 2, ',', ' ') }}
<div class="fs-6">
<span class="badge bg-secondary">{{ $contract->payment_type }}</span>
</div>
</div>
</div>
<div class="col col-md-6">
<div class="fs-6 text-secondary">Вознаграждение</div>
<div class="fw-bold fs-5 text-truncate text-dark-emphasis">
{{ number_format(GetAgentPaymentForContract($contract), 2, ',', ' ') }}</div>
</div>
</div>
</div>
<div class="col col-lg-4 p-3">
<div class="rounded-4 ratio ratio-1x1 rounded overflow-hidden" style="background-color:#ebeef5"
data-base64="">
@if ($contract->base64_image)
<div class="w-100 h-100 d-flex align-items-center overflow-hidden zoomable-container"
style='background-image:url("data:image/jpeg;base64, {{ $contract->base64_image }}");'>
<div class="text-center mx-auto zoom-button">
<svg class="text-white" xmlns="http://www.w3.org/2000/svg" width="70" height="70"
fill="currentColor" class="bi bi-zoom-in" viewBox="0 0 16 16">
<path fill-rule="evenodd"
d="M6.5 12a5.5 5.5 0 1 0 0-11 5.5 5.5 0 0 0 0 11M13 6.5a6.5 6.5 0 1 1-13 0 6.5 6.5 0 0 1 13 0" />
<path
d="M10.344 11.742q.044.06.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1 1 0 0 0-.115-.1 6.5 6.5 0 0 1-1.398 1.4z" />
<path fill-rule="evenodd"
d="M6.5 3a.5.5 0 0 1 .5.5V6h2.5a.5.5 0 0 1 0 1H7v2.5a.5.5 0 0 1-1 0V7H3.5a.5.5 0 0 1 0-1H6V3.5a.5.5 0 0 1 .5-.5" />
</svg>
</div>
</div>
@endif
</div>
</div>
</div>
<div class="row pt-3 border-1 border-top">
<div class="col">
<div class="fs-6 text-secondary hstack gap-2">
<span>Комментарий к договору</span>
@if ($contract->comment)
<a class="ms-auto" href="#" data-bs-toggle="modal" data-bs-target="#contractCommentModal">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" 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>
@endif
</div>
@if ($contract->comment)
<div class="fw-bold fs-5 text-dark-emphasis">{{ $contract->comment }}</div>
@else
<div><a href="#" data-bs-toggle="modal" data-bs-target="#contractCommentModal">Оставьте
комментарий</a>, он будет виден
только вам</div>
@endif
</div>
</div>
</div>
<div class="col-12 col-lg-4 py-3 text-dark" style="background-color:#eef5fb">
<div class="fw-bold fs-5 mb-3">История договора</div>
@if ($contract->deal->notifications->count() == 0)
<div class="d-flex justify-content-center align-items-center">
<div class="d-grid gap-1 p-3">
<i class="bi bi-inbox display-5 text-center"></i>
<span class="fs-6 fw-semibold">{{ __('notifications.has no history') }}</span>
</div>
</div>
@else
@foreach ($contract->deal->notifications as $notification)
<div class="d-flex flex-row mb-3">
<div class="pe-2">
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="currentColor"
class="bi bi-record-circle-fill align-middle" viewBox="0 0 16 16">
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0m-8 3a3 3 0 1 0 0-6 3 3 0 0 0 0 6" />
</svg>
</div>
<div class="">
<div class="text-secondary">{{ $notification->created_at }}</div>
<div class="fw-bold text-dark-emphasis">{{ $notification->data['text'] }}</div>
</div>
</div>
@endforeach
@endif
</div>
<!-- Modal -->
<div class="modal fade" id="contractCommentModal" tabindex="-1" aria-labelledby="exampleModalLabel"
aria-hidden="true">
<form class="modal-dialog" method="post"
action="{{ route('contract.comment', ['contract' => $contract]) }}">
<div class="modal-content">
<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="contractComment" class="form-label">Ваш комментарий</label>
<textarea class="form-control" id="contractComment" name="comment" rows="3">{{ $contract->comment }}</textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
<button type="submit" class="btn btn-primary">Сохранить</button>
</div>
</div>
</form>
</div>
</div>
<style>
.zoomable-container {
backdrop-filter: blur(10px);
background-repeat: no-repeat;
background-size: contain;
background-position: center;
}
.zoomable-container:not(.zoomed):hover {
transform: scale(1.5);
background-color: rgb(33 33 33 / 60%);
background-blend-mode: multiply;
background-repeat: no-repeat;
background-position: center;
}
.zoomable-container.zoomed {
position: fixed;
top: 0;
left: 0;
margin: 0 auto;
}
.zoomable-container.zoomed .zoom-button {
display: none;
}
</style>
<script>
var images = document.querySelectorAll(".zoomable-container");
Array.prototype.forEach.call(images, function(img) {
// Do stuff here
img.addEventListener('click', function() {
console.log(img.classList.contains('zoomed'));
if (!img.classList.contains('zoomed')) {
img.classList.add('zoomed');
} else {
img.classList.remove('zoomed');
}
});
});
</script>
@endsection

View File

@ -16,13 +16,18 @@
<tbody class="">
@foreach ($contracts as $contract)
<tr class="d-flex d-lg-table-row flex-column flex-md-row my-4 my-lg-0" scope="row">
<td class="align-middle d-flex flex-row">
<td class="align-middle d-flex flex-row d-lg-table-cell">
<div class="d-block d-md-none me-2">
<div class="border rounded-pill "
style="width: 1.5rem; height:1.5rem;background-color:{{ $statuses::getHtmlColor($contract->status) }}">
</div>
</div>
<div class="w-100">{{ $contract->deal->user->name }}</div>
<div class="w-100">
@php($clients = $contract->deal->clients)
@foreach ($clients as $client)
<div>{{ $client->name }}</div>
@endforeach
</div>
</td>
<td class="d-block d-lg-none d-flex flex-row">
<div class="w-100">

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::create('deal_clients', function (Blueprint $table)
{
$table->id();
$table->foreignId(column: 'deal_id')->references('id')->on('deals')->onDelete('cascade');
$table->foreignId(column: 'client_id')->references('id')->on('users');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('docs');
}
};

View File

@ -81,7 +81,7 @@ protected static function booted(): void
}
private function notify()
{
$this->user->notify(new AgentCreated($this));
//$this->user->notify(new AgentCreated($this));
return true;
}
}

View File

@ -6,10 +6,12 @@
use Illuminate\Database\Eloquent\Model;
use Modules\Payment\Traits\Paymentable;
use Modules\Main\Models\City;
use Modules\Bitrix\Traits\Bitrixable;
class Company extends Model
{
use HasFactory;
use Paymentable;
use Bitrixable;
const STATUS_NEW = 'new';
const STATUS_ACCEPTED = 'accepted';
const STATUS_DECLINED = 'declined';

View File

@ -1,6 +1,6 @@
<?php
namespace App\Models\Deal;
namespace Modules\Main\Models\Deal;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
@ -9,22 +9,39 @@
use Laravel\Sanctum\HasApiTokens;
use Modules\User\Models\User;
use App\Models\Deal\Deal;
use Modules\Main\Models\Agent\Agent;
use Modules\Bitrix\Traits\Bitrixable;
class Client extends User
{
use Bitrixable;
protected $table = 'users';
public function deals()
{
return $this->hasMany(Deal::class, 'client_id');
}
return $this->hasManyThrough(
Deal::class
,
DealClients::class
,
'client_id'
,
'id'
,
'id'
,
'deal_id'
);
}
public function dealsWithContracts()
{
return $this->hasMany(Deal::class, 'client_id')->whereHas('contracts');
return $this->hasManyThrough(Deal::class, DealClients::class)->whereHas('contracts');
}
public function ofAgent(Agent $agent)
{
return $this->deals->where('agent_id', $agent->id);
}
public function create()
{
}
}

View File

@ -0,0 +1,66 @@
<?php
namespace Modules\Main\Models\Deal;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable;
use Modules\User\Models\User;
use Modules\User\Models\UserRole;
use Modules\User\Models\Role;
use Modules\Contracts\Models\Contract;
use Modules\Main\Models\Complex;
use Modules\Main\Models\Agent\Agent;
use Modules\Bitrix\Traits\Bitrixable;
class Deal extends Model
{
use HasFactory, Notifiable, Bitrixable;
protected $fillable = [
'complex_id',
'agent_id',
'bitrix_id',
'is_unique',
'confirm_token'
];
public function complex()
{
return $this->belongsTo(Complex::class, 'complex_id');
}
public function clients()
{
return $this->hasManyThrough(
Client::class
,
DealClients::class
,
'deal_id'
,
'id'
,
'id'
,
'client_id'
);
//return $this->hasMany(DealClients::class);
}
public function agent()
{
return $this->belongsTo(Agent::class, 'agent_id');
}
public function contract()
{
return $this->hasOne(Contract::class, 'deal_id');
}
protected static function booted(): void
{
static::creating(function (Deal $deal)
{
$deal->confirm_token = hash('sha256', json_encode($deal->all()));
});
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace Modules\Main\Models\Deal;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
use Modules\User\Models\User;
use Modules\User\Models\UserRole;
use Modules\User\Models\Role;
use Modules\Main\Models\Agent\Agent;
use Modules\Bitrix\Traits\Bitrixable;
class DealClients extends User
{
use Bitrixable;
protected $table = 'deal_clients';
protected $fillable = ['deal_id', 'client_id'];
public function deal()
{
return $this->belongsTo(Deal::class);
}
public function client()
{
return $this->belongsTo(related: Client::class);
}
protected static function booted(): void
{
static::created(function (DealClients $dealClient)
{
UserRole::create([
'user_id' => $dealClient->client_id,
'role_id' => Role::CLIENT
]);
});
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace Modules\Main\Models\Deal;
final class DealStatus
{
public const NEW = 'NEW';
public const MODERATION = 'MODERATION';
public const UNIQUE = 'UNIQUE';
public const NOT_UNIQUE = 'NOT UNIQUE';
}

View File

@ -4,7 +4,7 @@
@if ($clients->count() == 0)
<div class="text-center py-5">Нет данных для отображения</div>
@endif
<div class="vstack gap-3">
<div class="vstack gap-2">
@foreach ($clients as $client)
<?php
$complexesNames = [];
@ -19,7 +19,6 @@
$complexesNames = implode(', ', array: $complexesNames);
$dealsWithContracts = $client->deals()->whereHas('contract');
?>
@if ($mode == 'full')
<div class="d-flex flex-row m-0 my-2 px-2 client-row">
<div class="d-flex flex-column flex-md-row w-100">
@ -35,7 +34,6 @@
{{ $client->phone }}
</div>
</div>
<div class="col col-md-1 text-end contracts-link">
@if ($dealsWithContracts->count() == 1)
<a href="{{ route('contract', ['contract' => $dealsWithContracts->first()->contract]) }}"
@ -60,7 +58,7 @@ class="icon-link icon-link-hover">
</div>
</div>
@else
<div class="m-2">
<div class="m-2 my-1">
@if ($dealsWithContracts->count() == 1)
<a href="{{ route('contract', ['contract' => $dealsWithContracts->first()->contract]) }}"
class="icon-link icon-link-hover w-100 hstack gap-2 text-decoration-none">
@ -90,6 +88,10 @@ class="icon-link icon-link-hover w-100 hstack gap-2 text-decoration-none">
d="M1 8a.5.5 0 0 1 .5-.5h11.793l-3.147-3.146a.5.5 0 0 1 .708-.708l4 4a.5.5 0 0 1 0 .708l-4 4a.5.5 0 0 1-.708-.708L13.293 8.5H1.5A.5.5 0 0 1 1 8" />
</svg>
</a>
@else
<a href="" class="icon-link icon-link-hover w-100 hstack gap-2 text-decoration-none">
<span class="text-dark">{{ $client->name }}</span>
</a>
@endif
</div>
@endif

View File

@ -8,8 +8,8 @@
</div>
</div>
<div class="row d-none d-md-flex">
<div class="col-12 col-lg-6 mb-2">
<div class="" data-bs-toggle="modal" data-bs-target="#plan7Modal">
<div class="col-12 col-lg-6 d-flex flex-column">
<div class="mb-auto" data-bs-toggle="modal" data-bs-target="#plan7Modal">
<a href="#"
class="list-group-item list-group-item-action p-3 bg-white rounded border border-light-subtle"
aria-current="true">
@ -27,7 +27,7 @@ class="list-group-item list-group-item-action p-3 bg-white rounded border border
</div>
</a>
</div>
<div class="mt-3">
<div class="">
<div class="hstack gap-2">
<div class="fs-5 fw-bold">Клиенты</div>
<div class="ms-auto p-2">
@ -40,7 +40,7 @@ class="list-group-item list-group-item-action p-3 bg-white rounded border border
</div>
</div>
<div class="col-12 col-lg-6">
<div class="p-2 px-sm-4 py-sm-3 bg-primary border rounded-3 mx-auto" style="min-height:30em;">
<div class="p-2 px-sm-4 py-sm-3 bg-primary border rounded-3 mx-auto h-100" style="min-height:30em;">
<div class="h4 pb-2 mb-2 fw-bold ">
<small>Добавить клиента</small>
</div>

View File

@ -39,7 +39,6 @@
{
Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('home');
Route::get('/clients/table', [App\Http\Controllers\ClientsTableController::class, 'index'])->name('clients.table');
Route::get('/contract/{contract}', [App\Http\Controllers\Deal\ContractController::class, 'index'])->name('contract');
Route::get('/company/details/{company?}', [App\Http\Controllers\Company\DetailsController::class, 'index'])->name('company.details');
Route::post('/company/{company}/details/', [App\Http\Controllers\Company\DetailsController::class, 'store'])->name('company.details.store');