feat: implement tour management logic and add store/update form requests

This commit is contained in:
2026-08-20 12:00:47 +03:30
parent 021a66cfc9
commit be06e60f2c
3 changed files with 522 additions and 1 deletions
+329 -1
View File
@@ -2,10 +2,338 @@
namespace App\Http\Controllers\Admin;
use App\Enums\Admin\Tour\TourScope;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\Tour\TourStoreRequest;
use App\Http\Requests\Admin\Tour\TourUpdateRequest;
use App\Models\City;
use App\Models\Country;
use App\Models\File;
use App\Models\Tour;
use App\Models\TourDeparture;
use App\Models\TourImage;
use Hekmatinasser\Verta\Verta;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class TourController extends Controller
{
//
public function index(Request $request)
{
$title = 'جزئیات تور';
$search = $request->input('search');
$sort = $request->input('sort');
$tours = Tour::query()
->with([
'originCity',
'city',
'file',
])
->withCount('tourDepartures')
->when($search, function ($query) use ($search) {
$query->where(function ($query) use ($search) {
$query->where('title', 'like', "%{$search}%")
->orWhere('summary', 'like', "%{$search}%")
->orWhereHas('originCity', function ($query) use ($search) {
$query->where('name', 'like', "%{$search}%");
})
->orWhereHas('city', function ($query) use ($search) {
$query->where('name', 'like', "%{$search}%");
});
});
})
->applySort($sort)
->paginate(3)
->withQueryString();
return view('admin.tours.index', compact('title', 'tours'));
}
public function create()
{
$title = 'ایجاد تور';
$cities = City::query()
->orderBy('name')
->get();
$countries = Country::query()
->orderBy('name')
->get();
return view('admin.tours.create', compact('title', 'cities', 'countries'));
}
public function citiesByCountry(Country $country)
{
$cities = City::query()
->where('county_id', $country->id)
->orderBy('name')
->get(['id', 'name']);
return response()->json($cities);
}
public function store(TourStoreRequest $request)
{
$data = $request->validated();
try {
$startDate = Verta::parseFormat('Y/m/d H:i', $data['start_date'])->toCarbon();
$endDate = Verta::parseFormat('Y/m/d H:i', $data['end_date'])->toCarbon();
} catch (\Throwable $e) {
return back()
->withErrors(['start_date' => 'فرمت تاریخ و ساعت وارد شده معتبر نیست.'])
->withInput();
}
if ($startDate < now()) {
return back()
->withErrors(['start_date' => 'تاریخ حرکت نمی‌تواند قبل از زمان و ساعت جاری باشد.'])
->withInput();
}
if ($endDate <= $startDate) {
return back()
->withErrors(['end_date' => 'تاریخ برگشت باید بعد از تاریخ حرکت باشد.'])
->withInput();
}
DB::beginTransaction();
try {
$tour = Tour::create([
'title' => $data['title'],
'summary' => $data['summary'] ?? null,
'description' => $data['description'] ?? null,
'origin_city_id' => $data['origin_city_id'],
'destination_city_id' => $data['destination_city_id'],
'tour_scope' => $data['tour_scope'],
'tour_mode' => $data['tour_mode'],
'tour_type' => $data['tour_type'],
'views_count' => 0,
]);
TourDeparture::create([
'tour_id' => $tour->id,
'start_date' => $startDate,
'end_date' => $endDate,
'price' => $data['price'],
'capacity' => $data['capacity'],
'status' => 1,
]);
if (!empty($data['images'])) {
$isDefault = true;
foreach ($data['images'] as $index => $image) {
$imageName = $tour->id . '_' . time() . rand(11111, 99999) . '.' . $image->extension();
$path = $image->storeAs('tour_images', $imageName, 'public');
$file = File::create([
'name' => $imageName,
'size' => $image->getSize(),
'original_name' => $image->getClientOriginalName(),
'path' => $path,
'extension' => $image->extension()
]);
TourImage::create([
'tour_id' => $tour->id,
'file_id' => $file->id,
'sort_order' => $index + 1,
'is_default' => $isDefault
]);
if ($isDefault) {
$isDefault = false;
}
}
}
DB::commit();
return redirect()
->route('admin.tours.index')
->with('success', 'تور با موفقیت ایجاد شد.');
} catch (\Throwable $e) {
DB::rollBack();
Log::error('خطا در ایجاد تور: ' . $e->getMessage());
return back()
->withErrors(['error' => 'خطایی در ثبت اطلاعات رخ داد. لطفاً دوباره تلاش کنید.'])
->withInput();
}
}
public function edit(Tour $tour)
{
$title = 'ویرایش تور';
$tour->load(['originCity', 'city', 'images', 'tourDepartures']);
$departure = $tour->tourDepartures->first();
$cities = City::query()->orderBy('name')->get();
$countries = Country::query()->orderBy('name')->get();
$selectedCountryId = null;
$destinationCities = $cities;
if ($tour->tour_scope === TourScope::INTERNATIONAL && $tour->city) {
$selectedCountryId = $tour->city->county_id;
$destinationCities = City::query()
->where('county_id', $selectedCountryId)
->orderBy('name')
->get();
}
return view('admin.tours.edit', compact(
'title', 'tour', 'departure', 'cities', 'countries',
'selectedCountryId', 'destinationCities'
));
}
public function update(TourUpdateRequest $request, Tour $tour)
{
$data = $request->validated();
try {
$startDate = Verta::parse($data['start_date'])->datetime();
$endDate = Verta::parse($data['end_date'])->datetime();
} catch (\Throwable $e) {
return back()
->withErrors(['start_date' => 'فرمت تاریخ و ساعت وارد شده معتبر نیست.'])
->withInput();
}
if ($endDate <= $startDate) {
return back()
->withErrors(['end_date' => 'تاریخ برگشت باید بعد از تاریخ حرکت باشد.'])
->withInput();
}
DB::beginTransaction();
try {
$tour->update([
'title' => $data['title'],
'summary' => $data['summary'] ?? null,
'description' => $data['description'] ?? null,
'origin_city_id' => $data['origin_city_id'],
'destination_city_id' => $data['destination_city_id'],
'tour_scope' => $data['tour_scope'],
'tour_mode' => $data['tour_mode'],
'tour_type' => $data['tour_type'],
]);
$departure = $tour->tourDepartures()->first();
if ($departure) {
$departure->update([
'start_date' => $startDate,
'end_date' => $endDate,
'price' => $data['price'],
'capacity' => $data['capacity'],
]);
} else {
TourDeparture::create([
'tour_id' => $tour->id,
'start_date' => $startDate,
'end_date' => $endDate,
'price' => $data['price'],
'capacity' => $data['capacity'],
'status' => 1,
]);
}
if (!empty($data['images'])) {
$hasDefault = $tour->images()->where('is_default', true)->exists();
$nextSort = (int)$tour->images()->max('sort_order');
foreach ($data['images'] as $index => $image) {
$imageName = $tour->id . '_' . time() . rand(11111, 99999) . '.' . $image->extension();
$path = $image->storeAs('tour_images', $imageName, 'public');
$file = File::create([
'name' => $imageName,
'size' => $image->getSize(),
'original_name' => $image->getClientOriginalName(),
'path' => $path,
'extension' => $image->extension(),
]);
TourImage::create([
'tour_id' => $tour->id,
'file_id' => $file->id,
'sort_order' => $nextSort + $index + 1,
'is_default' => !$hasDefault && $index === 0,
]);
}
}
DB::commit();
return redirect()
->route('admin.tours.index')
->with('success', 'تور با موفقیت ویرایش شد.');
} catch (\Exception $exception) {
DB::rollBack();
Log::error('خطا در ویرایش تور: ' . $exception->getMessage());
return back()
->withErrors(['error' => 'خطایی در ثبت اطلاعات رخ داد. لطفاً دوباره تلاش کنید.'])
->withInput();
}
}
public function trash()
{
$title = 'تورهای حذف شده';
$tours = Tour::onlyTrashed()
->with([
'originCity',
'city',
'coverImage.file',
])
->orderByDesc('deleted_at')
->paginate(4);
return view('admin.tours.trash', compact('title', 'tours'));
}
public function destroy(Tour $tour)
{
$tour->delete();
return redirect()
->route('admin.tours.index')
->with('success', 'تور با موفقیت به سطل زباله منتقل شد.');
}
public function restore( $id)
{
$tour = Tour::onlyTrashed()->findOrFail($id);
$tour->restore();
return redirect()
->route('admin.tours.trash.index')
->with('success', 'تور با موفقیت بازگردانی شد.');
}
public function forceDelete($id)
{
$tour = Tour::onlyTrashed()->findOrFail($id);
$tour->forceDelete();
return redirect()
->route('admin.tours.trash.index')
->with('success', 'تور به‌صورت دائمی حذف شد.');
}
}