master
Rustem 2025-08-10 00:06:01 +05:00
parent ae3bd41178
commit 6ad0fe85e6
4 changed files with 304 additions and 71 deletions

View File

@ -242,12 +242,14 @@ class MobileApiController extends Controller
}
}
public function checkAvailableCar($started_at = null, $ended_at = null, $modelId = null, $color = null): bool {
if (!$started_at || !$ended_at || !$modelId) {
public function checkAvailableCar($started_at = null, $ended_at = null, $modelId = null, $color = null): bool|JsonResponse
{
if (!$started_at || !$ended_at || !$modelId || !$color) {
$request = request();
$started_at = $started_at ?? $request->input('started_at');
$ended_at = $ended_at ?? $request->input('ended_at');
$modelId = $modelId ?? $request->input('model_id');
$color = Unimodel::model('pipi_auto_colors')->where('code', $request->input('color'))->first()->id;
}
if (!$started_at || !$ended_at || !$modelId) {
@ -259,7 +261,7 @@ class MobileApiController extends Controller
$cars = UniModel::model('pipi_auto')
->where('is_inactive', '=', false)
->where('model_id', $modelId)
->where('color_id', $color->id)
->where('color_id', $color)
->get();
$busyCars = UniModel::model('pipi_auto_calendar')
->whereBetween('date', [$started_at, $ended_at])
@ -268,7 +270,7 @@ class MobileApiController extends Controller
$availableCars = $cars->reject(fn($car) => in_array($car->id, $busyCars));
return $availableCars->isNotEmpty();
return response()->json($availableCars->isNotEmpty());
}
public function getAvailableMarksList(Request $request): JsonResponse
@ -341,7 +343,7 @@ class MobileApiController extends Controller
$colorPath = url('api/files/file/' . ltrim($photo->path, '/'));
}
if (!$this->checkAvailableCar($started_at, $ended_at, $mark->id, $carColor)) {
if (!$this->checkAvailableCar($started_at, $ended_at, $mark->id, $carColor->code)) {
continue;
}

View File

@ -9,6 +9,7 @@
namespace App\Modules\main\Components;
use A7kz\Platform\Models\UniModel;
use A7kz\Platform\Modules\Platform\Core\Services\Base\Component;
use Illuminate\Support\Facades\Request;
use Illuminate\Support\Carbon;
@ -21,21 +22,11 @@ class Main extends Component
parent::__construct($params, $module);
$this->template = 'pipicar::main.views.main';
$class_filters = Request::input('class_filters') ?? null;
$started_at = Request::input('started_at') ?? null;
$ended_at = Request::input('ended_at') ?? null;
$bodywork_filters = Request::input('bodywork_filters') ?? null;
$request = request();
$request = new \Illuminate\Http\Request([
'started_at' => $started_at ?? Carbon::now()->toDateString(),
'ended_at' => $ended_at ?? Carbon::now()->addDays(3)->toDateString(),
'class' => $class_filters ?? null,
'bodywork' => $bodywork_filters ?? null,
]);
$response = $this->getAuto($request);
$response = (new \App\Http\Controllers\MobileApiController)->getAvailableMarksList($request);
$this->params['cars'] = collect($response->getData(true))->filter(function ($item) {
$this->params['cars'] = collect($response)->filter(function ($item) {
return !empty($item['tariffs']);
})->values();
@ -50,4 +41,142 @@ class Main extends Component
// return !empty($item['tariffs']);
// })->values();
}
private function getAuto($request)
{
$started_at = $request->query('started_at');
$ended_at = $request->query('ended_at');
$class = $request->query('class');
$bodywork = $request->query('bodywork');
$marks = UniModel::model('pipi_brand_models')->get();
$availableMarks = [];
if ($class) {
$class_id = UniModel::model('pipi_auto_classes')->where('name', $class)->first()?->id;
$marks = $marks->where('class_id', $class_id);
}
if ($bodywork) {
$bodywork_id = UniModel::model('pipi_auto_bodywork')->where('name', $bodywork)->first()?->id;
$marks = $marks->where('bodywork_id', $bodywork_id);
}
foreach ($marks as $mark) {
if ($mark->name === 'Ввод остатков') {
continue;
}
$tariffs = UniModel::model('pipi_auto_tariffs')->where('model_id', $mark->id)->get();
if ($tariffs->isEmpty()) {
continue;
}
$cars = UniModel::model('pipi_auto')->where('model_id', $mark->id)->get();
if ($cars->isEmpty()) {
continue;
}
$brand = UniModel::model('pipi_auto_brands')->find($mark->brand_id);
$tariffs = UniModel::model('pipi_auto_tariffs')->where('model_id', $mark->id)->get();
$equipment = UniModel::model('pipi_auto_equipment')->where('id', $mark->equipment_id)->first();
$class = UniModel::model('pipi_auto_classes')->find($mark->class_id);
$bodywork = UniModel::model('pipi_auto_bodywork')->find($mark->bodywork_id);
$photo = UniModel::model('core_files')->find($mark?->photo_id);
$path = null;
if ($photo) {
$path = url('api/files/file/' . ltrim($photo->path, '/'));
}
$tariffs_new = [];
foreach ($tariffs as $tariff) {
$tariffs_new[] = [
'name' => $tariff?->name,
'price' => $tariff?->base_rate,
'min' => $tariff?->day_range_start,
'max' => $tariff?->day_range_end,
'deposit' => $tariff?->deposit,
];
}
$carsByColor = $cars->groupBy('color_id');
foreach ($carsByColor as $carColor => $carsOfColor) {
$carColor = UniModel::model('pipi_auto_colors')->find($carColor);
$colorPath = $path;
if ($carsOfColor->first()->photo_id) {
$photo = UniModel::model('core_files')->find($carsOfColor->first()->photo_id);
$colorPath = url('api/files/file/' . ltrim($photo->path, '/'));
}
$key = $mark->name . '-' . $mark->year . '-' . $carColor->code;
$availableMarks[$key] = [
'id' => $mark->id,
'brand' => $brand?->name,
'mark' => $mark->name,
'year' => $mark->year,
'color' => $carColor->code,
'configuration' => $equipment?->name,
'people' => $mark?->people ?? '5',
'actuator' => $mark?->actuator ?? 'Передний',
'fuel_type' => $mark?->fuel_type ?? 'АКПП',
'hp' => $mark?->hp ?? '1.6',
'engine_capacity' => $mark?->engine_capacity ?? '1591',
'fuel_tank' => $mark?->fuel_tank ?? '50',
'class' => $class->name ?? 'Эконом',
'bodywork' => $bodywork->name ?? 'Седан',
'deposit' => $tariffs_new[0]['deposit'] ?? 30000,
'conditioner' => $mark?->conditioner,
'photo' => $colorPath,
'free' => $this->checkAvailableCar($started_at, $ended_at, $mark->id, $carColor),
'tariffs' => $tariffs_new,
];
}
}
if (empty($availableMarks)) {
$availableMarks = (object) [];
}
return $availableMarks;
}
private function checkAvailableCar($started_at = null, $ended_at = null, $modelId = null, $color = null): bool {
if (!$started_at || !$ended_at || !$modelId) {
$request = request();
$started_at = $started_at ?? $request->input('started_at');
$ended_at = $ended_at ?? $request->input('ended_at');
$modelId = $modelId ?? $request->input('model_id');
}
if (is_null($started_at) or is_null($ended_at)) {
$started_at = Carbon::now()->format('Y-m-d');
$ended_at = Carbon::now()->addDays(3)->format('Y-m-d');
}
$started_at = Carbon::parse($started_at)->format('Y-m-d');
$ended_at = Carbon::parse($ended_at)->format('Y-m-d');
$cars = UniModel::model('pipi_auto')
->where('is_inactive', '=', false)
->where('model_id', $modelId)
->where('color_id', $color->id)
->get();
$busyCars = UniModel::model('pipi_auto_calendar')
->whereBetween('date', [$started_at, $ended_at])
->pluck('auto_id')
->toArray();
$availableCars = $cars->reject(fn($car) => in_array($car->id, $busyCars));
return $availableCars->isNotEmpty();
}
public function action_rent()
{
$request = Request::input();
dd($request);
}
}

View File

@ -1,16 +1,18 @@
{
"admin": [
"default",
"add",
"show",
"edit",
"delete"
],
"user": [
"default",
"add",
"show",
"edit",
"delete"
]
"admin": [
"default",
"add",
"show",
"edit",
"delete",
"rent"
],
"user": [
"default",
"add",
"show",
"edit",
"delete",
"rent"
]
}

View File

@ -1,7 +1,10 @@
@php $request = \Illuminate\Support\Facades\Request::input();
$address = \A7kz\Platform\Models\UniModel::model('pipi_address')->get();
@endphp
<div class="container text-center main-page">
<div class="main-page-header">
<p class="top-title">Доступные автомобили</p>
<p class="title">Выберите лучший автомобиль</p>
<p class="top-title">@lang('Доступные автомобили')</p>
<p class="title">@lang('Выберите лучший автомобиль')</p>
</div>
<div class="container my-4">
<div class="row">
@ -115,11 +118,10 @@
<div class="row custom-row w-100">
@php $carsValues = array_values($cars->toArray()); @endphp
@foreach($carsValues as $car)
{{-- @dump($car)--}}
<div class="col-12 col-md-6 mb-4">
<div class="car-card flex-grow-1">
<img class="img-fluid" src="{{ $car['photo'] ?? asset('img/default.png') }}" alt="Car Image">
<div class="title">{{ $car['brand'] . ' ' . $car['mark'] . ' - ' . $car['year'] }}</div>
<div class="title">{{ $car['brand'] . ' ' . $car['mark'] . ' - ' . $car['year']. ' ' . \A7kz\Platform\Models\UniModel::model('pipi_auto_colors')->where('code', $car['color'])->first()?->name }}</div>
<div class="card-car-chars">
<div class="add-li"><i class="material-symbols-outlined">ac_unit</i><p>&nbsp;Кондиционер</p></div>
<div class="add-li"><i class="material-symbols-outlined">auto_transmission</i><p>&nbsp;{{ $car['fuel_type'] }}</p></div>
@ -140,24 +142,20 @@
@endif
</p>
</div>
<a href="#" class="btn btn-primary open-rent-modal"
data-bs-toggle="modal"
data-bs-target="#rentModal"
data-car='@json($car)'
data-photo="{{ $car['photo'] ?? asset('img/default.png') }}"
{{-- data-brand="{{ $car['brand'] }}"--}}
{{-- data-mark="{{ $car['mark'] }}"--}}
{{-- data-year="{{ $car['year'] }}"--}}
{{-- data-fuel="{{ $car['fuel_type'] }}"--}}
{{-- data-people="{{ $car['people'] }}"--}}
{{-- data-tariffWithMin1 ="{{ $tariffWithMin1['price'] ?? '' }}"--}}
{{-- data-tariffWithMin3="{{ $tariffWithMin3['price'] ?? '' }}"--}}
{{-- data-tariffWithMin6="{{ $tariffWithMin6['price'] ?? '' }}"--}}
{{-- data-tariffWithMin15="{{ $tariffWithMin15['price'] ?? '' }}"--}}
>
@lang('Арендовать')
</a>
@if($car['free'])
<a href="#" class="btn btn-primary open-rent-modal"
data-bs-toggle="modal"
data-bs-target="#rentModal"
data-car='@json($car)'
data-photo="{{ $car['photo'] ?? asset('img/default.png') }}"
>
@lang('Арендовать')
</a>
@else
<span class="badge bg-danger text-wrap" style="width: 150px;">
@lang('Машина занята выберете другую дату')
</span>
@endif
</div>
</div>
</div>
@ -173,7 +171,8 @@
<div class="modal-content">
<div class="modal-body">
<div class="row justify-content-center align-items-start">
<form method="post" class="col-12 col-md-7">
<form method="POST" class="col-12 col-md-7" action="{{ $app->getPath() . '/rent' }}">
@csrf
<input hidden="" name="mark_id" id="mark_id" value="">
<input hidden="" name="color" id="color" value="">
<div class="car-card d-flex flex-grow-1 flex-row mb-4">
@ -195,7 +194,7 @@
<div class="row justify-content-center align-items-end">
<div class="col-12 col-md-6">
<label for="days" class="form-label">Срок аренды, дней <b>*</b></label>
<input type="number" name="days" class="form-control" id="days" value="7" required="" min="1" step="1">
<input type="number" name="days" class="form-control" id="days" value="3" required="" min="1" step="1">
</div>
<div class="col-12 col-md-6">
<div class="form-text">
@ -224,10 +223,14 @@
<div class="card-body">
<div class="mb-3">
<label class="form-label">Место и время получения <b>*</b></label>
<input placeholder="Введите адрес получения" type="text" class="form-control" name="pick-up-address" id="pick-up-address" required="">
<select required class="form-select" name="pick-up-address" id="pick-up-address">
@foreach($address as $addres)
<option value="{{ $addres->id }}">{{ $addres->name }}</option>
@endforeach
</select>
<div class="row mt-3">
<div class="col-6">
<input type="date" class="form-control" id="pick-up-date" name="pick-up-date" required="" min="2025-05-02">
<input type="date" class="form-control" id="pick-up-date" name="pick-up-date" required="" min="{{now()->format('Y-m-d')}}">
</div>
<div class="col-6">
<input type="time" class="form-control" name="pick-up-time" id="pick-up-time" value="09:00" required="">
@ -237,7 +240,11 @@
<hr>
<div class="mb-3">
<label class="form-label">Место и время возврата <b>*</b></label>
<input placeholder="Введите адрес возврата" type="text" class="form-control" name="return-address" id="return-address" required="">
<select required class="form-select" id="return-address" name="return-address">
@foreach($address as $addres)
<option value="{{ $addres->id }}">{{ $addres->name }}</option>
@endforeach
</select>
<div class="row mt-3">
<div class="col-6">
<input type="date" class="form-control" id="return-date" name="return-date" disabled="" required="">
@ -250,7 +257,11 @@
</div>
</div>
<div class="mb-4 lst-btn">
<button type="submit" class="btn btn-primary btn-lg">Забронировать сейчас</button>
<button type="submit" class="btn btn-primary btn-lg" id="book-button" style="display: none;">
{{ __('Забронировать сейчас') }}</button>
<div id="not-available-message" class="alert alert-danger" style="display: none;">
{{ __('Машина на эти даты не доступна') }}
</div>
<p class="add-text2">Бесплатная отмена в любое время</p>
</div>
</form>
@ -353,22 +364,34 @@
const returnDateInput = document.getElementById('return-date');
const daysInput = document.getElementById('days');
const today = new Date();
const yyyy = today.getFullYear();
const mm = String(today.getMonth() + 1).padStart(2, '0');
const dd = String(today.getDate()).padStart(2, '0');
const todayStr = `${yyyy}-${mm}-${dd}`;
pickUpDateInput.value = todayStr;
pickUpDateInput.setAttribute('min', todayStr);
const urlParams = new URLSearchParams(window.location.search);
const pickUpDateParam = urlParams.get('started_at');
const returnDateParam = urlParams.get('ended_at');
if (returnDateParam) {
const [day, month, year] = returnDateParam.split('.');
returnDateInput.value = `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`;
}
daysInput.addEventListener('input', updateReturnDate);
pickUpDateInput.addEventListener('change', updateReturnDate);
const today = new Date();
let initialPickUpDate = today.toISOString().split('T')[0];
if (pickUpDateParam) {
const [day, month, year] = pickUpDateParam.split('.');
initialPickUpDate = `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`;
}
pickUpDateInput.value = initialPickUpDate;
daysInput.addEventListener('input', () => updateReturnDate());
pickUpDateInput.addEventListener('change', () => updateReturnDate());
document.querySelectorAll('.open-rent-modal').forEach(button => {
button.addEventListener('click', function () {
const car = JSON.parse(this.getAttribute('data-car'));
modal.setAttribute('data-car', JSON.stringify(car));
daysInput.addEventListener('input', () => checkAvailableCar(car));
pickUpDateInput.addEventListener('change', () => checkAvailableCar(car));
document.getElementById('modalCarImage').src = this.getAttribute('data-photo');
document.getElementById('modalCarTitle').innerText = `${car.brand} ${car.mark} - ${car.year}`;
document.getElementById('modalFuel').innerHTML = `&nbsp;${car.fuel_type}`;
@ -383,11 +406,27 @@
document.getElementById('tariffWithMin16').innerHTML = `&nbsp;${getTariffPrice(16)} ₸`;
document.getElementById('tariffWithMin30').innerHTML = `&nbsp;${(getTariffPrice(1) * 0.6).toFixed(0)} ₸`;
updateReturnDate();
updateReturnDate(true);
checkAvailableCar(car);
});
});
function updateReturnDate() {
function updateReturnDate(isInitialLoad = false) {
if (isInitialLoad && pickUpDateParam && returnDateParam) {
const [day1, month1, year1] = pickUpDateParam.split('.');
const [day2, month2, year2] = returnDateParam.split('.');
const pickUpDate = new Date(`${year1}-${month1}-${day1}`);
const returnDate = new Date(`${year2}-${month2}-${day2}`);
const timeDiff = returnDate - pickUpDate;
const rentalDays = Math.ceil(timeDiff / (1000 * 60 * 60 * 24));
if (!isNaN(rentalDays) && rentalDays > 0) {
daysInput.value = rentalDays;
}
}
const pickUpDate = new Date(pickUpDateInput.value);
const rentalDays = parseInt(daysInput.value, 10);
@ -402,6 +441,67 @@
calculateOther(rentalDays);
}
function checkAvailableCar(car) {
const started_at = document.getElementById('pick-up-date').value;
const ended_at = document.getElementById('return-date').value;
const bookButton = document.getElementById('book-button');
const notAvailableMessage = document.getElementById('not-available-message');
bookButton.style.display = 'none';
notAvailableMessage.style.display = 'none';
if (!started_at || !ended_at) {
console.log('Даты не заполнены');
return;
}
fetch('{{ url('/api/mobile/checkAvailableCar') }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-CSRF-TOKEN': '{{ csrf_token() }}',
},
body: JSON.stringify({
'started_at': started_at,
'ended_at': ended_at,
'model_id': car.id,
'color': car.color
})
})
.then(async response => {
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
return response.json();
} else {
const text = await response.text();
if (text === 'true') return true;
if (text === 'false') return false;
throw new Error(text || 'Неверный формат ответа');
}
})
.then(result => {
const isAvailable =
result === true ||
(typeof result === 'object' && result?.return?.success) ||
(typeof result === 'string' && result.toLowerCase() === 'true');
if (isAvailable) {
bookButton.style.display = 'block';
} else {
notAvailableMessage.style.display = 'block';
notAvailableMessage.textContent = 'Машина на эти даты не доступна';
}
})
.catch(error => {
console.error('Ошибка:', error);
notAvailableMessage.style.display = 'block';
notAvailableMessage.textContent = 'Ошибка при проверке доступности';
});
}
function calculateOther(days) {
if (!days || isNaN(days) || days < 1) {
updatePrices(0, 0, 0, 0);