migrate to gtea from bistbucket
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Repositories;
|
||||
|
||||
use App\Abstracts\EntityRepository;
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Pagination\Paginator;
|
||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Modules\Booking\Models\Floor;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class FloorRepository extends EntityRepository
|
||||
{
|
||||
public string $table = Floor::TABLE_NAME;
|
||||
|
||||
protected array $fillableColumns = [
|
||||
'restaurant_id',
|
||||
'hotel_id',
|
||||
'name',
|
||||
'level',
|
||||
'status',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'deleted_at',
|
||||
];
|
||||
|
||||
protected function getQuery(): Builder
|
||||
{
|
||||
return parent::getQuery();
|
||||
}
|
||||
|
||||
protected function getFilterData(array $filterData = []): array
|
||||
{
|
||||
$defaultArgs = [
|
||||
'perPage' => 10,
|
||||
'search' => '',
|
||||
'orderBy' => 'id',
|
||||
'order' => 'desc',
|
||||
'with_deleted' => false,
|
||||
];
|
||||
|
||||
return array_merge($defaultArgs, $filterData);
|
||||
}
|
||||
|
||||
private function getFloorQuery(): Builder
|
||||
{
|
||||
return $this->getQuery()
|
||||
->select(
|
||||
"{$this->table}.id",
|
||||
"{$this->table}.restaurant_id",
|
||||
"{$this->table}.hotel_id",
|
||||
'hotels.name as hotel_name',
|
||||
"{$this->table}.name",
|
||||
"{$this->table}.level",
|
||||
"{$this->table}.status",
|
||||
"{$this->table}.created_at",
|
||||
"{$this->table}.deleted_at"
|
||||
)
|
||||
->leftJoin('hotels', 'hotels.id', '=', "{$this->table}.hotel_id");
|
||||
}
|
||||
|
||||
protected function filterSearchQuery(Builder|EloquentBuilder $query, string $searchedText): Builder
|
||||
{
|
||||
$searchable = "%$searchedText%";
|
||||
|
||||
return $query->where("{$this->table}.name", 'LIKE', $searchable)
|
||||
->orWhere("{$this->table}.status", 'LIKE', $searchable);
|
||||
}
|
||||
|
||||
public function getAll(array $filterData = []): Paginator
|
||||
{
|
||||
$filter = $this->getFilterData($filterData);
|
||||
$query = $this->getFloorQuery();
|
||||
|
||||
if (! $filter['with_deleted']) {
|
||||
$query->whereNull("{$this->table}.deleted_at");
|
||||
}
|
||||
|
||||
if (! empty($filter['search'])) {
|
||||
$query = $this->filterSearchQuery($query, $filter['search']);
|
||||
}
|
||||
|
||||
return $query
|
||||
->orderBy($filter['orderBy'], $filter['order'])
|
||||
->paginate($filter['perPage']);
|
||||
}
|
||||
|
||||
public function getCount(array $filterData = []): int
|
||||
{
|
||||
$filter = $this->getFilterData($filterData);
|
||||
$query = $this->getQuery();
|
||||
|
||||
if (! $filter['with_deleted']) {
|
||||
$query->whereNull("{$this->table}.deleted_at");
|
||||
}
|
||||
|
||||
return $query->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getByColumn(string $columnName, $columnValue, array $selects = ['*']): ?object
|
||||
{
|
||||
$item = $this->getFloorQuery()
|
||||
->where($columnName, $columnValue)
|
||||
->first($selects);
|
||||
|
||||
if (empty($item)) {
|
||||
throw new Exception(
|
||||
$this->getExceptionMessage(static::MESSAGE_ITEM_DOES_NOT_EXIST_MESSAGE),
|
||||
Response::HTTP_NOT_FOUND
|
||||
);
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function create(array $data): object
|
||||
{
|
||||
$data = $this->prepareForDB($data);
|
||||
$id = $this->getQuery()->insertGetId($data);
|
||||
|
||||
return Floor::find($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function update(int $id, array $data): object
|
||||
{
|
||||
$item = Floor::findOrFail($id);
|
||||
$data = $this->prepareForDB($data, $item);
|
||||
parent::update($id, $data);
|
||||
|
||||
return $this->getById($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function prepareForDB(array $data, ?object $item = null): array
|
||||
{
|
||||
$data = parent::prepareForDB($data, $item);
|
||||
|
||||
if (empty($item)) {
|
||||
$data['created_at'] = now();
|
||||
$data['restaurant_id'] = $this->getCurrentRestaurantId();
|
||||
$data['status'] = 1;
|
||||
|
||||
if (! empty($data['image']) && $data['image'] instanceof \Illuminate\Http\UploadedFile) {
|
||||
$data['image'] = fileUploader('Booking/', 'png', $data['image']);
|
||||
}
|
||||
} else {
|
||||
if (! empty($data['image']) && $data['image'] instanceof \Illuminate\Http\UploadedFile) {
|
||||
$data['image'] = fileUploader('Booking/', 'png', $data['image'], $item->image);
|
||||
}
|
||||
|
||||
$data['updated_at'] = now();
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function getExceptionMessages(): array
|
||||
{
|
||||
return [
|
||||
static::MESSAGE_ITEM_DOES_NOT_EXIST_MESSAGE => 'Floor does not exist.',
|
||||
static::MESSAGE_ITEM_COULD_NOT_BE_DELETED => 'Floor could not be deleted.',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Repositories;
|
||||
|
||||
use App\Abstracts\EntityRepository;
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Pagination\Paginator;
|
||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Modules\Booking\Models\Hotel;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class HotelRepository extends EntityRepository
|
||||
{
|
||||
public string $table = Hotel::TABLE_NAME;
|
||||
|
||||
protected array $fillableColumns = [
|
||||
'restaurant_id',
|
||||
'name',
|
||||
'location',
|
||||
'email',
|
||||
'phone',
|
||||
'description',
|
||||
'latitude',
|
||||
'longitude',
|
||||
'check_in_time',
|
||||
'check_out_time',
|
||||
'status',
|
||||
];
|
||||
|
||||
protected function getQuery(): Builder
|
||||
{
|
||||
return parent::getQuery();
|
||||
}
|
||||
|
||||
protected function getFilterData(array $filterData = []): array
|
||||
{
|
||||
$defaultArgs = [
|
||||
'perPage' => 10,
|
||||
'search' => '',
|
||||
'orderBy' => 'id',
|
||||
'order' => 'desc',
|
||||
'with_deleted' => false,
|
||||
];
|
||||
|
||||
return array_merge($defaultArgs, $filterData);
|
||||
}
|
||||
|
||||
private function getHotelQuery(): Builder
|
||||
{
|
||||
return $this->getQuery()
|
||||
->select(
|
||||
"{$this->table}.id",
|
||||
"{$this->table}.restaurant_id",
|
||||
"{$this->table}.name",
|
||||
"{$this->table}.location",
|
||||
"{$this->table}.email",
|
||||
"{$this->table}.phone",
|
||||
"{$this->table}.description",
|
||||
"{$this->table}.latitude",
|
||||
"{$this->table}.longitude",
|
||||
"{$this->table}.check_in_time",
|
||||
"{$this->table}.check_out_time",
|
||||
"{$this->table}.status",
|
||||
"{$this->table}.created_at",
|
||||
"{$this->table}.deleted_at"
|
||||
);
|
||||
}
|
||||
|
||||
protected function filterSearchQuery(Builder|EloquentBuilder $query, string $searchedText): Builder
|
||||
{
|
||||
$searchable = "%$searchedText%";
|
||||
|
||||
return $query->where("{$this->table}.name", 'LIKE', $searchable)
|
||||
->orWhere("{$this->table}.status", 'LIKE', $searchable);
|
||||
}
|
||||
|
||||
public function getAll(array $filterData = []): Paginator
|
||||
{
|
||||
$filter = $this->getFilterData($filterData);
|
||||
$query = $this->getHotelQuery();
|
||||
|
||||
if (! $filter['with_deleted']) {
|
||||
$query->whereNull("{$this->table}.deleted_at");
|
||||
}
|
||||
|
||||
if (! empty($filter['search'])) {
|
||||
$query = $this->filterSearchQuery($query, $filter['search']);
|
||||
}
|
||||
|
||||
return $query
|
||||
->orderBy($filter['orderBy'], $filter['order'])
|
||||
->paginate($filter['perPage']);
|
||||
}
|
||||
|
||||
public function getCount(array $filterData = []): int
|
||||
{
|
||||
$filter = $this->getFilterData($filterData);
|
||||
$query = $this->getQuery();
|
||||
|
||||
if (! $filter['with_deleted']) {
|
||||
$query->whereNull("{$this->table}.deleted_at");
|
||||
}
|
||||
|
||||
return $query->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getByColumn(string $columnName, $columnValue, array $selects = ['*']): ?object
|
||||
{
|
||||
$item = $this->getHotelQuery()
|
||||
->where($columnName, $columnValue)
|
||||
->first($selects);
|
||||
|
||||
if (empty($item)) {
|
||||
throw new Exception(
|
||||
$this->getExceptionMessage(static::MESSAGE_ITEM_DOES_NOT_EXIST_MESSAGE),
|
||||
Response::HTTP_NOT_FOUND
|
||||
);
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function create(array $data): object
|
||||
{
|
||||
$data = $this->prepareForDB($data);
|
||||
$id = $this->getQuery()->insertGetId($data);
|
||||
|
||||
return Hotel::find($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function update(int $id, array $data): object
|
||||
{
|
||||
$item = Hotel::findOrFail($id);
|
||||
$data = $this->prepareForDB($data, $item);
|
||||
parent::update($id, $data);
|
||||
|
||||
return $this->getById($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function prepareForDB(array $data, ?object $item = null): array
|
||||
{
|
||||
$data = parent::prepareForDB($data, $item);
|
||||
|
||||
if (empty($item)) {
|
||||
$data['created_at'] = now();
|
||||
$data['restaurant_id'] = $this->getCurrentRestaurantId();
|
||||
$data['status'] = 1;
|
||||
|
||||
if (! empty($data['image']) && $data['image'] instanceof \Illuminate\Http\UploadedFile) {
|
||||
$data['image'] = fileUploader('Booking/', 'png', $data['image']);
|
||||
}
|
||||
} else {
|
||||
if (! empty($data['image']) && $data['image'] instanceof \Illuminate\Http\UploadedFile) {
|
||||
$data['image'] = fileUploader('Booking/', 'png', $data['image'], $item->image);
|
||||
}
|
||||
|
||||
$data['updated_at'] = now();
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function getExceptionMessages(): array
|
||||
{
|
||||
return [
|
||||
static::MESSAGE_ITEM_DOES_NOT_EXIST_MESSAGE => 'Hotel does not exist.',
|
||||
static::MESSAGE_ITEM_COULD_NOT_BE_DELETED => 'Hotel could not be deleted.',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Repositories;
|
||||
|
||||
use App\Abstracts\EntityRepository;
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Pagination\Paginator;
|
||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Modules\Booking\Models\RoomAvailability;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class RoomAvailabilityRepository extends EntityRepository
|
||||
{
|
||||
public string $table = RoomAvailability::TABLE_NAME;
|
||||
|
||||
protected array $fillableColumns = [
|
||||
'restaurant_id',
|
||||
'hotel_id',
|
||||
'room_id',
|
||||
'date',
|
||||
'total_inventory',
|
||||
'available_inventory',
|
||||
'is_available',
|
||||
'is_closed_to_arrival',
|
||||
'is_closed_to_departure',
|
||||
'base_price',
|
||||
'extra_adult_price',
|
||||
'extra_child_price',
|
||||
'min_stay',
|
||||
'max_stay',
|
||||
'blocked_by_booking_id',
|
||||
'block_type',
|
||||
'note',
|
||||
'status',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'deleted_at',
|
||||
];
|
||||
|
||||
protected function getQuery(): Builder
|
||||
{
|
||||
return parent::getQuery();
|
||||
}
|
||||
|
||||
protected function getFilterData(array $filterData = []): array
|
||||
{
|
||||
$defaultArgs = [
|
||||
'perPage' => 10,
|
||||
'search' => '',
|
||||
'orderBy' => 'id',
|
||||
'order' => 'desc',
|
||||
'with_deleted' => false,
|
||||
];
|
||||
|
||||
return array_merge($defaultArgs, $filterData);
|
||||
}
|
||||
|
||||
private function getRoomAvailabilityQuery(): Builder
|
||||
{
|
||||
return $this->getQuery()
|
||||
->select(
|
||||
'room_availabilities.id',
|
||||
'room_availabilities.restaurant_id',
|
||||
'room_availabilities.hotel_id',
|
||||
'hotels.name as hotel_name',
|
||||
'room_availabilities.room_id',
|
||||
'rooms.room_number',
|
||||
'rooms.room_code',
|
||||
'rooms.slug as room_slug',
|
||||
'rooms.max_adults',
|
||||
'rooms.max_children',
|
||||
'room_types.name as room_type_name',
|
||||
'floors.name as floor_name',
|
||||
'floors.level as floor_level',
|
||||
'room_availabilities.date',
|
||||
'room_availabilities.total_inventory',
|
||||
'room_availabilities.available_inventory',
|
||||
'room_availabilities.is_available',
|
||||
'room_availabilities.is_closed_to_arrival',
|
||||
'room_availabilities.is_closed_to_departure',
|
||||
'room_availabilities.base_price',
|
||||
'room_availabilities.extra_adult_price',
|
||||
'room_availabilities.extra_child_price',
|
||||
'room_availabilities.min_stay',
|
||||
'room_availabilities.max_stay',
|
||||
'room_availabilities.blocked_by_booking_id',
|
||||
'room_availabilities.block_type',
|
||||
'room_availabilities.note',
|
||||
'room_availabilities.status',
|
||||
'room_availabilities.created_at',
|
||||
'room_availabilities.updated_at',
|
||||
'room_availabilities.deleted_at'
|
||||
)
|
||||
->leftJoin('rooms', 'room_availabilities.room_id', '=', 'rooms.id')
|
||||
->leftJoin('hotels', 'room_availabilities.hotel_id', '=', 'hotels.id')
|
||||
->leftJoin('floors', 'rooms.floor_id', '=', 'floors.id')
|
||||
->leftJoin('room_types', 'rooms.room_type_id', '=', 'room_types.id');
|
||||
}
|
||||
|
||||
protected function filterSearchQuery(Builder|EloquentBuilder $query, string $searchedText): Builder
|
||||
{
|
||||
$searchable = "%$searchedText%";
|
||||
|
||||
return $query->where("{$this->table}.name", 'LIKE', $searchable)
|
||||
->orWhere("{$this->table}.status", 'LIKE', $searchable);
|
||||
}
|
||||
|
||||
public function getAll(array $filterData = []): Paginator
|
||||
{
|
||||
$filter = $this->getFilterData($filterData);
|
||||
$query = $this->getRoomAvailabilityQuery();
|
||||
|
||||
if (! $filter['with_deleted']) {
|
||||
$query->whereNull("{$this->table}.deleted_at");
|
||||
}
|
||||
|
||||
if (! empty($filter['search'])) {
|
||||
$query = $this->filterSearchQuery($query, $filter['search']);
|
||||
}
|
||||
|
||||
return $query
|
||||
->orderBy($filter['orderBy'], $filter['order'])
|
||||
->paginate($filter['perPage']);
|
||||
}
|
||||
|
||||
public function getCount(array $filterData = []): int
|
||||
{
|
||||
$filter = $this->getFilterData($filterData);
|
||||
$query = $this->getQuery();
|
||||
|
||||
if (! $filter['with_deleted']) {
|
||||
$query->whereNull("{$this->table}.deleted_at");
|
||||
}
|
||||
|
||||
return $query->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getByColumn(string $columnName, $columnValue, array $selects = ['*']): ?object
|
||||
{
|
||||
$item = $this->getRoomAvailabilityQuery()
|
||||
->where($columnName, $columnValue)
|
||||
->first($selects);
|
||||
|
||||
if (empty($item)) {
|
||||
throw new Exception(
|
||||
$this->getExceptionMessage(static::MESSAGE_ITEM_DOES_NOT_EXIST_MESSAGE),
|
||||
Response::HTTP_NOT_FOUND
|
||||
);
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function create(array $data): object
|
||||
{
|
||||
$data = $this->prepareForDB($data);
|
||||
$id = $this->getQuery()->insertGetId($data);
|
||||
|
||||
return RoomAvailability::find($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function update(int $id, array $data): object
|
||||
{
|
||||
$item = RoomAvailability::findOrFail($id);
|
||||
$data = $this->prepareForDB($data, $item);
|
||||
parent::update($id, $data);
|
||||
|
||||
return $this->getById($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function prepareForDB(array $data, ?object $item = null): array
|
||||
{
|
||||
$data = parent::prepareForDB($data, $item);
|
||||
|
||||
if (empty($item)) {
|
||||
$data['created_at'] = now();
|
||||
$data['restaurant_id'] = $this->getCurrentRestaurantId();
|
||||
$data['status'] = 1;
|
||||
|
||||
if (! empty($data['image']) && $data['image'] instanceof \Illuminate\Http\UploadedFile) {
|
||||
$data['image'] = fileUploader('Booking/', 'png', $data['image']);
|
||||
}
|
||||
} else {
|
||||
if (! empty($data['image']) && $data['image'] instanceof \Illuminate\Http\UploadedFile) {
|
||||
$data['image'] = fileUploader('Booking/', 'png', $data['image'], $item->image);
|
||||
}
|
||||
|
||||
$data['updated_at'] = now();
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function getExceptionMessages(): array
|
||||
{
|
||||
return [
|
||||
static::MESSAGE_ITEM_DOES_NOT_EXIST_MESSAGE => 'RoomAvailability does not exist.',
|
||||
static::MESSAGE_ITEM_COULD_NOT_BE_DELETED => 'RoomAvailability could not be deleted.',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Repositories;
|
||||
|
||||
use App\Abstracts\EntityRepository;
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Pagination\Paginator;
|
||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Modules\Booking\Models\RoomBlock;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class RoomBlockRepository extends EntityRepository
|
||||
{
|
||||
public string $table = RoomBlock::TABLE_NAME;
|
||||
|
||||
protected array $fillableColumns = [
|
||||
'restaurant_id',
|
||||
'hotel_id',
|
||||
'room_id',
|
||||
'start_date',
|
||||
'end_date',
|
||||
'start_time',
|
||||
'end_time',
|
||||
'block_type',
|
||||
'blocked_inventory',
|
||||
'priority',
|
||||
'reason',
|
||||
'auto_release',
|
||||
'created_by',
|
||||
'status',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'deleted_at',
|
||||
];
|
||||
|
||||
protected function getQuery(): Builder
|
||||
{
|
||||
return parent::getQuery();
|
||||
}
|
||||
|
||||
protected function getFilterData(array $filterData = []): array
|
||||
{
|
||||
$defaultArgs = [
|
||||
'perPage' => 10,
|
||||
'search' => '',
|
||||
'orderBy' => 'id',
|
||||
'order' => 'desc',
|
||||
'with_deleted' => false,
|
||||
];
|
||||
|
||||
return array_merge($defaultArgs, $filterData);
|
||||
}
|
||||
|
||||
private function getRoomBlockQuery(): Builder
|
||||
{
|
||||
return $this->getQuery()
|
||||
->select(
|
||||
'room_blocks.id',
|
||||
'room_blocks.restaurant_id',
|
||||
'room_blocks.hotel_id',
|
||||
'hotels.name as hotel_name',
|
||||
'room_blocks.room_id',
|
||||
'rooms.room_number',
|
||||
'rooms.room_code',
|
||||
'rooms.slug as room_slug',
|
||||
'rooms.max_adults',
|
||||
'rooms.max_children',
|
||||
'room_types.name as room_type_name',
|
||||
'floors.name as floor_name',
|
||||
'floors.level as floor_level',
|
||||
'room_blocks.start_date',
|
||||
'room_blocks.end_date',
|
||||
'room_blocks.start_time',
|
||||
'room_blocks.end_time',
|
||||
'room_blocks.block_type',
|
||||
'room_blocks.blocked_inventory',
|
||||
'room_blocks.priority',
|
||||
'room_blocks.reason',
|
||||
'room_blocks.auto_release',
|
||||
'room_blocks.created_by',
|
||||
'room_blocks.status',
|
||||
'room_blocks.created_at',
|
||||
'room_blocks.updated_at',
|
||||
'room_blocks.deleted_at'
|
||||
)
|
||||
->leftJoin('rooms', 'room_blocks.room_id', '=', 'rooms.id')
|
||||
->leftJoin('hotels', 'room_blocks.hotel_id', '=', 'hotels.id')
|
||||
->leftJoin('floors', 'rooms.floor_id', '=', 'floors.id')
|
||||
->leftJoin('room_types', 'rooms.room_type_id', '=', 'room_types.id');
|
||||
}
|
||||
|
||||
protected function filterSearchQuery(Builder|EloquentBuilder $query, string $searchedText): Builder
|
||||
{
|
||||
$searchable = "%$searchedText%";
|
||||
|
||||
return $query->where("{$this->table}.name", 'LIKE', $searchable)
|
||||
->orWhere("{$this->table}.status", 'LIKE', $searchable);
|
||||
}
|
||||
|
||||
public function getAll(array $filterData = []): Paginator
|
||||
{
|
||||
$filter = $this->getFilterData($filterData);
|
||||
$query = $this->getRoomBlockQuery();
|
||||
|
||||
if (! $filter['with_deleted']) {
|
||||
$query->whereNull("{$this->table}.deleted_at");
|
||||
}
|
||||
|
||||
if (! empty($filter['search'])) {
|
||||
$query = $this->filterSearchQuery($query, $filter['search']);
|
||||
}
|
||||
|
||||
return $query
|
||||
->orderBy($filter['orderBy'], $filter['order'])
|
||||
->paginate($filter['perPage']);
|
||||
}
|
||||
|
||||
public function getCount(array $filterData = []): int
|
||||
{
|
||||
$filter = $this->getFilterData($filterData);
|
||||
$query = $this->getQuery();
|
||||
|
||||
if (! $filter['with_deleted']) {
|
||||
$query->whereNull("{$this->table}.deleted_at");
|
||||
}
|
||||
|
||||
return $query->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getByColumn(string $columnName, $columnValue, array $selects = ['*']): ?object
|
||||
{
|
||||
$item = $this->getRoomBlockQuery()
|
||||
->where($columnName, $columnValue)
|
||||
->first($selects);
|
||||
|
||||
if (empty($item)) {
|
||||
throw new Exception(
|
||||
$this->getExceptionMessage(static::MESSAGE_ITEM_DOES_NOT_EXIST_MESSAGE),
|
||||
Response::HTTP_NOT_FOUND
|
||||
);
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function create(array $data): object
|
||||
{
|
||||
$data = $this->prepareForDB($data);
|
||||
$id = $this->getQuery()->insertGetId($data);
|
||||
|
||||
return RoomBlock::find($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function update(int $id, array $data): object
|
||||
{
|
||||
$item = RoomBlock::findOrFail($id);
|
||||
$data = $this->prepareForDB($data, $item);
|
||||
parent::update($id, $data);
|
||||
|
||||
return $this->getById($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function prepareForDB(array $data, ?object $item = null): array
|
||||
{
|
||||
$data = parent::prepareForDB($data, $item);
|
||||
|
||||
if (empty($item)) {
|
||||
$data['created_at'] = now();
|
||||
$data['restaurant_id'] = $this->getCurrentRestaurantId();
|
||||
$data['status'] = 1;
|
||||
} else {
|
||||
$data['updated_at'] = now();
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function getExceptionMessages(): array
|
||||
{
|
||||
return [
|
||||
static::MESSAGE_ITEM_DOES_NOT_EXIST_MESSAGE => 'RoomBlock does not exist.',
|
||||
static::MESSAGE_ITEM_COULD_NOT_BE_DELETED => 'RoomBlock could not be deleted.',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Repositories;
|
||||
|
||||
use App\Abstracts\EntityRepository;
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Pagination\Paginator;
|
||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Modules\Booking\Models\Room;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class RoomRepository extends EntityRepository
|
||||
{
|
||||
public string $table = Room::TABLE_NAME;
|
||||
|
||||
protected array $fillableColumns = [
|
||||
'restaurant_id',
|
||||
'hotel_id',
|
||||
'floor_id',
|
||||
'room_type_id',
|
||||
'room_number',
|
||||
'room_code',
|
||||
'slug',
|
||||
'max_adults',
|
||||
'max_children',
|
||||
'has_balcony',
|
||||
'has_bathtub',
|
||||
'view_type',
|
||||
'image',
|
||||
'banner_image',
|
||||
'gallery_images',
|
||||
'description',
|
||||
'regular_price',
|
||||
'offer_price',
|
||||
'is_clean',
|
||||
'is_available',
|
||||
'status',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'deleted_at',
|
||||
];
|
||||
|
||||
protected function getQuery(): Builder
|
||||
{
|
||||
return parent::getQuery();
|
||||
}
|
||||
|
||||
protected function getFilterData(array $filterData = []): array
|
||||
{
|
||||
$defaultArgs = [
|
||||
'perPage' => 10,
|
||||
'search' => '',
|
||||
'orderBy' => 'id',
|
||||
'order' => 'desc',
|
||||
'with_deleted' => false,
|
||||
];
|
||||
|
||||
return array_merge($defaultArgs, $filterData);
|
||||
}
|
||||
|
||||
private function getRoomQuery(): Builder
|
||||
{
|
||||
return $this->getQuery()
|
||||
->select(
|
||||
'rooms.id',
|
||||
'rooms.restaurant_id',
|
||||
'rooms.hotel_id',
|
||||
'hotels.name as hotel_name',
|
||||
'rooms.floor_id',
|
||||
'floors.name as floor_name',
|
||||
'floors.level as floor_level',
|
||||
'rooms.room_type_id',
|
||||
'room_types.name as room_type_name',
|
||||
'rooms.room_number',
|
||||
'rooms.room_code',
|
||||
'rooms.slug',
|
||||
'rooms.max_adults',
|
||||
'rooms.max_children',
|
||||
'rooms.has_balcony',
|
||||
'rooms.has_bathtub',
|
||||
'rooms.view_type',
|
||||
'rooms.image',
|
||||
'rooms.banner_image',
|
||||
'rooms.gallery_images',
|
||||
'rooms.description',
|
||||
'rooms.regular_price',
|
||||
'rooms.offer_price',
|
||||
'rooms.is_clean',
|
||||
'rooms.is_available',
|
||||
'rooms.status',
|
||||
'rooms.created_at',
|
||||
'rooms.deleted_at'
|
||||
)
|
||||
->leftJoin('hotels', 'rooms.hotel_id', '=', 'hotels.id')
|
||||
->leftJoin('floors', 'rooms.floor_id', '=', 'floors.id')
|
||||
->leftJoin('room_types', 'rooms.room_type_id', '=', 'room_types.id');
|
||||
}
|
||||
|
||||
protected function filterSearchQuery(Builder|EloquentBuilder $query, string $searchedText): Builder
|
||||
{
|
||||
$searchable = "%$searchedText%";
|
||||
|
||||
return $query->where("{$this->table}.name", 'LIKE', $searchable)
|
||||
->orWhere("{$this->table}.status", 'LIKE', $searchable);
|
||||
}
|
||||
|
||||
public function getAll(array $filterData = []): Paginator
|
||||
{
|
||||
$filter = $this->getFilterData($filterData);
|
||||
$query = $this->getRoomQuery();
|
||||
|
||||
if (! $filter['with_deleted']) {
|
||||
$query->whereNull("{$this->table}.deleted_at");
|
||||
}
|
||||
|
||||
if (! empty($filter['search'])) {
|
||||
$query = $this->filterSearchQuery($query, $filter['search']);
|
||||
}
|
||||
|
||||
return $query
|
||||
->orderBy($filter['orderBy'], $filter['order'])
|
||||
->paginate($filter['perPage']);
|
||||
}
|
||||
|
||||
public function getCount(array $filterData = []): int
|
||||
{
|
||||
$filter = $this->getFilterData($filterData);
|
||||
$query = $this->getQuery();
|
||||
|
||||
if (! $filter['with_deleted']) {
|
||||
$query->whereNull("{$this->table}.deleted_at");
|
||||
}
|
||||
|
||||
return $query->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getByColumn(string $columnName, $columnValue, array $selects = ['*']): ?object
|
||||
{
|
||||
$item = $this->getRoomQuery()
|
||||
->where($columnName, $columnValue)
|
||||
->first($selects);
|
||||
|
||||
if (empty($item)) {
|
||||
throw new Exception(
|
||||
$this->getExceptionMessage(static::MESSAGE_ITEM_DOES_NOT_EXIST_MESSAGE),
|
||||
Response::HTTP_NOT_FOUND
|
||||
);
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function create(array $data): object
|
||||
{
|
||||
$data = $this->prepareForDB($data);
|
||||
$id = $this->getQuery()->insertGetId($data);
|
||||
|
||||
return Room::find($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function update(int $id, array $data): object
|
||||
{
|
||||
$item = Room::findOrFail($id);
|
||||
$data = $this->prepareForDB($data, $item);
|
||||
parent::update($id, $data);
|
||||
|
||||
return $this->getById($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function prepareForDB(array $data, ?object $item = null): array
|
||||
{
|
||||
$data = parent::prepareForDB($data, $item);
|
||||
|
||||
if (empty($item)) {
|
||||
$data['created_at'] = now();
|
||||
$data['restaurant_id'] = $this->getCurrentRestaurantId();
|
||||
|
||||
if (! empty($data['image']) && $data['image'] instanceof \Illuminate\Http\UploadedFile) {
|
||||
$data['image'] = fileUploader('rooms/', 'png', $data['image']);
|
||||
}
|
||||
|
||||
if (! empty($data['banner_image']) && $data['banner_image'] instanceof \Illuminate\Http\UploadedFile) {
|
||||
$data['banner_image'] = fileUploader('rooms/', 'png', $data['banner_image']);
|
||||
}
|
||||
|
||||
// Multiple gallery images
|
||||
if (! empty($data['gallery_images']) && is_array($data['gallery_images'])) {
|
||||
$gallery = [];
|
||||
foreach ($data['gallery_images'] as $file) {
|
||||
if ($file instanceof \Illuminate\Http\UploadedFile) {
|
||||
$gallery[] = fileUploader('rooms/', 'png', $file);
|
||||
}
|
||||
}
|
||||
$data['gallery_images'] = ! empty($gallery) ? json_encode($gallery) : null;
|
||||
}
|
||||
} else {
|
||||
if (! empty($data['image']) && $data['image'] instanceof \Illuminate\Http\UploadedFile) {
|
||||
$data['image'] = fileUploader('rooms/', 'png', $data['image'], $item->image);
|
||||
}
|
||||
|
||||
if (! empty($data['banner_image']) && $data['banner_image'] instanceof \Illuminate\Http\UploadedFile) {
|
||||
$data['banner_image'] = fileUploader('rooms/', 'png', $data['banner_image'], $item->banner_image);
|
||||
}
|
||||
|
||||
// Gallery images update
|
||||
if (! empty($data['gallery_images']) && is_array($data['gallery_images'])) {
|
||||
$gallery = [];
|
||||
|
||||
// Keep old images if item exists
|
||||
if (! empty($item->gallery_images)) {
|
||||
$gallery = is_string($item->gallery_images)
|
||||
? json_decode($item->gallery_images, true)
|
||||
: (array) $item->gallery_images;
|
||||
}
|
||||
|
||||
foreach ($data['gallery_images'] as $file) {
|
||||
if ($file instanceof \Illuminate\Http\UploadedFile) {
|
||||
$gallery[] = fileUploader('rooms/', 'png', $file);
|
||||
}
|
||||
}
|
||||
|
||||
$data['gallery_images'] = ! empty($gallery) ? json_encode($gallery) : null;
|
||||
} else {
|
||||
unset($data['gallery_images']); // keep existing
|
||||
}
|
||||
|
||||
$data['updated_at'] = now();
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function getExceptionMessages(): array
|
||||
{
|
||||
return [
|
||||
static::MESSAGE_ITEM_DOES_NOT_EXIST_MESSAGE => 'Room does not exist.',
|
||||
static::MESSAGE_ITEM_COULD_NOT_BE_DELETED => 'Room could not be deleted.',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Repositories;
|
||||
|
||||
use App\Abstracts\EntityRepository;
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Pagination\Paginator;
|
||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Modules\Booking\Models\RoomType;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class RoomTypeRepository extends EntityRepository
|
||||
{
|
||||
public string $table = RoomType::TABLE_NAME;
|
||||
|
||||
protected array $fillableColumns = [
|
||||
'restaurant_id',
|
||||
'hotel_id',
|
||||
'name',
|
||||
'capacity',
|
||||
'beds',
|
||||
'bed_type',
|
||||
'has_ac',
|
||||
'has_wifi',
|
||||
'has_breakfast',
|
||||
'is_refundable',
|
||||
'base_price',
|
||||
'weekend_price',
|
||||
'extra_guest_price',
|
||||
'description',
|
||||
'status',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'deleted_at',
|
||||
];
|
||||
|
||||
protected function getQuery(): Builder
|
||||
{
|
||||
return parent::getQuery();
|
||||
}
|
||||
|
||||
protected function getFilterData(array $filterData = []): array
|
||||
{
|
||||
$defaultArgs = [
|
||||
'perPage' => 10,
|
||||
'search' => '',
|
||||
'orderBy' => 'id',
|
||||
'order' => 'desc',
|
||||
'with_deleted' => false,
|
||||
];
|
||||
|
||||
return array_merge($defaultArgs, $filterData);
|
||||
}
|
||||
|
||||
private function getRoomTypeQuery(): Builder
|
||||
{
|
||||
return $this->getQuery()
|
||||
->select(
|
||||
"{$this->table}.id",
|
||||
"{$this->table}.restaurant_id",
|
||||
"{$this->table}.hotel_id",
|
||||
'hotels.name as hotel_name',
|
||||
"{$this->table}.name",
|
||||
"{$this->table}.capacity",
|
||||
"{$this->table}.beds",
|
||||
"{$this->table}.bed_type",
|
||||
"{$this->table}.has_ac",
|
||||
"{$this->table}.has_wifi",
|
||||
"{$this->table}.has_breakfast",
|
||||
"{$this->table}.is_refundable",
|
||||
"{$this->table}.base_price",
|
||||
"{$this->table}.weekend_price",
|
||||
"{$this->table}.extra_guest_price",
|
||||
"{$this->table}.description",
|
||||
"{$this->table}.status",
|
||||
"{$this->table}.created_at",
|
||||
"{$this->table}.deleted_at"
|
||||
)
|
||||
->leftJoin('hotels', 'hotels.id', '=', "{$this->table}.hotel_id");
|
||||
}
|
||||
|
||||
protected function filterSearchQuery(Builder|EloquentBuilder $query, string $searchedText): Builder
|
||||
{
|
||||
$searchable = "%$searchedText%";
|
||||
|
||||
return $query->where("{$this->table}.name", 'LIKE', $searchable)
|
||||
->orWhere("{$this->table}.status", 'LIKE', $searchable);
|
||||
}
|
||||
|
||||
public function getAll(array $filterData = []): Paginator
|
||||
{
|
||||
$filter = $this->getFilterData($filterData);
|
||||
$query = $this->getRoomTypeQuery();
|
||||
|
||||
if (! $filter['with_deleted']) {
|
||||
$query->whereNull("{$this->table}.deleted_at");
|
||||
}
|
||||
|
||||
if (! empty($filter['search'])) {
|
||||
$query = $this->filterSearchQuery($query, $filter['search']);
|
||||
}
|
||||
|
||||
return $query
|
||||
->orderBy($filter['orderBy'], $filter['order'])
|
||||
->paginate($filter['perPage']);
|
||||
}
|
||||
|
||||
public function getCount(array $filterData = []): int
|
||||
{
|
||||
$filter = $this->getFilterData($filterData);
|
||||
$query = $this->getQuery();
|
||||
|
||||
if (! $filter['with_deleted']) {
|
||||
$query->whereNull("{$this->table}.deleted_at");
|
||||
}
|
||||
|
||||
return $query->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getByColumn(string $columnName, $columnValue, array $selects = ['*']): ?object
|
||||
{
|
||||
$item = $this->getRoomTypeQuery()
|
||||
->where($columnName, $columnValue)
|
||||
->first($selects);
|
||||
|
||||
if (empty($item)) {
|
||||
throw new Exception(
|
||||
$this->getExceptionMessage(static::MESSAGE_ITEM_DOES_NOT_EXIST_MESSAGE),
|
||||
Response::HTTP_NOT_FOUND
|
||||
);
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function create(array $data): object
|
||||
{
|
||||
$data = $this->prepareForDB($data);
|
||||
$id = $this->getQuery()->insertGetId($data);
|
||||
|
||||
return RoomType::find($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function update(int $id, array $data): object
|
||||
{
|
||||
$item = RoomType::findOrFail($id);
|
||||
$data = $this->prepareForDB($data, $item);
|
||||
parent::update($id, $data);
|
||||
|
||||
return $this->getById($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function prepareForDB(array $data, ?object $item = null): array
|
||||
{
|
||||
$data = parent::prepareForDB($data, $item);
|
||||
|
||||
if (empty($item)) {
|
||||
$data['created_at'] = now();
|
||||
$data['restaurant_id'] = $this->getCurrentRestaurantId();
|
||||
$data['status'] = 1;
|
||||
|
||||
if (! empty($data['image']) && $data['image'] instanceof \Illuminate\Http\UploadedFile) {
|
||||
$data['image'] = fileUploader('Booking/', 'png', $data['image']);
|
||||
}
|
||||
} else {
|
||||
if (! empty($data['image']) && $data['image'] instanceof \Illuminate\Http\UploadedFile) {
|
||||
$data['image'] = fileUploader('Booking/', 'png', $data['image'], $item->image);
|
||||
}
|
||||
|
||||
$data['updated_at'] = now();
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function getExceptionMessages(): array
|
||||
{
|
||||
return [
|
||||
static::MESSAGE_ITEM_DOES_NOT_EXIST_MESSAGE => 'RoomType does not exist.',
|
||||
static::MESSAGE_ITEM_COULD_NOT_BE_DELETED => 'RoomType could not be deleted.',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Http\Controllers\API;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Exception;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Modules\Booking\Http\Requests\Booking\BookingStoreRequest;
|
||||
use Modules\Booking\Http\Requests\Booking\BookingUpdateRequest;
|
||||
use Modules\Booking\Models\Booking;
|
||||
use Modules\Booking\Models\BookingItem;
|
||||
|
||||
class BookingController extends Controller
|
||||
{
|
||||
public function index(): JsonResponse
|
||||
{
|
||||
try {
|
||||
$filters = request()->all();
|
||||
|
||||
$query = Booking::with([
|
||||
'customer',
|
||||
'hotel',
|
||||
'bookingItems.room',
|
||||
]);
|
||||
|
||||
// -----------------------------
|
||||
// 🔍 SEARCHING
|
||||
// -----------------------------
|
||||
if (! empty($filters['search'])) {
|
||||
$search = $filters['search'];
|
||||
$query->whereHas('customer', function ($q) use ($search) {
|
||||
$q->where('name', 'LIKE', "%$search%")
|
||||
->orWhere('phone', 'LIKE', "%$search%");
|
||||
})->orWhere('id', $search);
|
||||
}
|
||||
|
||||
// -----------------------------
|
||||
// 🎯 FILTERS
|
||||
// -----------------------------
|
||||
if (isset($filters['status'])) {
|
||||
$query->where('status', $filters['status']);
|
||||
}
|
||||
|
||||
if (! empty($filters['customer_id'])) {
|
||||
$query->where('customer_id', $filters['customer_id']);
|
||||
}
|
||||
|
||||
if (! empty($filters['hotel_id'])) {
|
||||
$query->where('hotel_id', $filters['hotel_id']);
|
||||
}
|
||||
|
||||
if (! empty($filters['check_in_from']) && ! empty($filters['check_in_to'])) {
|
||||
$query->whereBetween('check_in', [$filters['check_in_from'], $filters['check_in_to']]);
|
||||
}
|
||||
|
||||
if (! empty($filters['check_out_from']) && ! empty($filters['check_out_to'])) {
|
||||
$query->whereBetween('check_out', [$filters['check_out_from'], $filters['check_out_to']]);
|
||||
}
|
||||
|
||||
// -----------------------------
|
||||
// 🔽 SORTING
|
||||
// -----------------------------
|
||||
$sortBy = $filters['sort_by'] ?? 'check_in';
|
||||
$sortOrder = $filters['sort_order'] ?? 'desc';
|
||||
$allowedSortColumns = ['id', 'check_in', 'check_out', 'status', 'created_at'];
|
||||
|
||||
if (! in_array($sortBy, $allowedSortColumns)) {
|
||||
$sortBy = 'check_in';
|
||||
}
|
||||
|
||||
$query->orderBy($sortBy, $sortOrder);
|
||||
|
||||
// -----------------------------
|
||||
// 📄 PAGINATION
|
||||
// -----------------------------
|
||||
$perPage = $filters['per_page'] ?? 20;
|
||||
$bookings = $query->paginate($perPage);
|
||||
|
||||
return $this->responseSuccess($bookings, 'Bookings fetched successfully.');
|
||||
} catch (Exception $e) {
|
||||
return $this->responseError([], $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function store(BookingStoreRequest $request): JsonResponse
|
||||
{
|
||||
$data = $request->validated();
|
||||
|
||||
DB::beginTransaction();
|
||||
|
||||
try {
|
||||
// Create main booking
|
||||
$booking = Booking::create([
|
||||
'restaurant_id' => getUserRestaurantId(),
|
||||
'hotel_id' => $data['hotel_id'],
|
||||
'customer_id' => $data['customer_id'],
|
||||
'check_in' => $data['check_in'],
|
||||
'check_out' => $data['check_out'],
|
||||
'check_in_time' => $data['check_in_time'] ?? null,
|
||||
'check_out_time' => $data['check_out_time'] ?? null,
|
||||
'total_adults' => $data['total_adults'],
|
||||
'total_children' => $data['total_children'],
|
||||
'subtotal_amount' => $data['subtotal_amount'],
|
||||
'tax_amount' => $data['tax_amount'] ?? 0,
|
||||
'discount_amount' => $data['discount_amount'] ?? 0,
|
||||
'total_amount' => $data['total_amount'],
|
||||
'payment_status' => $data['payment_status'],
|
||||
'payment_method' => $data['payment_method'] ?? null,
|
||||
'channel' => $data['channel'] ?? null,
|
||||
'status' => $data['status'],
|
||||
'remarks' => $data['remarks'] ?? null,
|
||||
]);
|
||||
|
||||
// Create booking items
|
||||
foreach ($data['booking_items'] as $item) {
|
||||
BookingItem::create([
|
||||
'restaurant_id' => getUserRestaurantId(),
|
||||
'booking_id' => $booking->id,
|
||||
'room_id' => $item['room_id'],
|
||||
'adults' => $item['adults'],
|
||||
'children' => $item['children'],
|
||||
'room_price' => $item['room_price'],
|
||||
'nights' => $item['nights'],
|
||||
'tax_amount' => $item['tax_amount'] ?? 0,
|
||||
'total_amount' => $item['total_amount'],
|
||||
'status' => $item['status'],
|
||||
]);
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
|
||||
return $this->responseSuccess($booking->load('bookingItems'), 'Booking has been created successfully.');
|
||||
} catch (Exception $e) {
|
||||
DB::rollBack();
|
||||
|
||||
return $this->responseError([], $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function show(int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$booking = Booking::with('customer', 'hotel', 'bookingItems.room')->findOrFail($id);
|
||||
|
||||
return $this->responseSuccess($booking, 'Booking has been fetched successfully.');
|
||||
} catch (Exception $e) {
|
||||
return $this->responseError([], $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function update(BookingUpdateRequest $request, int $id): JsonResponse
|
||||
{
|
||||
$data = $request->validated();
|
||||
$booking = Booking::findOrFail($id);
|
||||
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
// Update booking main data
|
||||
$booking->update($data);
|
||||
|
||||
if (isset($data['booking_items']) && is_array($data['booking_items'])) {
|
||||
$sentIds = collect($data['booking_items'])
|
||||
->pluck('id') // existing items should send 'id'
|
||||
->filter()
|
||||
->toArray();
|
||||
|
||||
// DELETE only removed items
|
||||
$booking->bookingItems()
|
||||
->whereNotIn('id', $sentIds)
|
||||
->delete();
|
||||
|
||||
foreach ($data['booking_items'] as $item) {
|
||||
if (! empty($item['id'])) {
|
||||
// → Update existing
|
||||
$booking->bookingItems()
|
||||
->where('id', $item['id'])
|
||||
->update([
|
||||
'restaurant_id' => $data['restaurant_id'] ?? $booking->restaurant_id,
|
||||
'room_id' => $item['room_id'],
|
||||
'adults' => $item['adults'],
|
||||
'children' => $item['children'],
|
||||
'room_price' => $item['room_price'],
|
||||
'nights' => $item['nights'],
|
||||
'tax_amount' => $item['tax_amount'] ?? 0,
|
||||
'total_amount' => $item['total_amount'],
|
||||
'status' => $item['status'] ?? 1,
|
||||
]);
|
||||
} else {
|
||||
// → Create new
|
||||
$booking->bookingItems()->create([
|
||||
'restaurant_id' => $data['restaurant_id'] ?? $booking->restaurant_id,
|
||||
'room_id' => $item['room_id'],
|
||||
'adults' => $item['adults'],
|
||||
'children' => $item['children'],
|
||||
'room_price' => $item['room_price'],
|
||||
'nights' => $item['nights'],
|
||||
'tax_amount' => $item['tax_amount'] ?? 0,
|
||||
'total_amount' => $item['total_amount'],
|
||||
'status' => $item['status'] ?? 1,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
|
||||
return $this->responseSuccess($booking->load('bookingItems.room'), 'Booking updated successfully.');
|
||||
} catch (Exception $e) {
|
||||
DB::rollBack();
|
||||
|
||||
return $this->responseError([], $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy(int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$booking = Booking::with('customer', 'hotel', 'bookingItems.room')->findOrFail($id);
|
||||
$booking->delete();
|
||||
|
||||
return $this->responseSuccess([], 'Booking has been deleted successfully.');
|
||||
} catch (Exception $e) {
|
||||
return $this->responseError([], $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Http\Controllers\API;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Exception;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Modules\Booking\Http\Requests\Floor\FloorStoreRequest;
|
||||
use Modules\Booking\Http\Requests\Floor\FloorUpdateRequest;
|
||||
use Modules\Booking\Repositories\FloorRepository;
|
||||
|
||||
class FloorController extends Controller
|
||||
{
|
||||
public function __construct(private FloorRepository $repo) {}
|
||||
|
||||
public function index(): JsonResponse
|
||||
{
|
||||
try {
|
||||
return $this->responseSuccess($this->repo->getAll(request()->all()), 'Floor has been fetched successfully.');
|
||||
} catch (Exception $e) {
|
||||
return $this->responseError([], $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function store(FloorStoreRequest $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
return $this->responseSuccess($this->repo->create($request->all()), 'Floor has been created successfully.');
|
||||
} catch (\Illuminate\Database\QueryException $exception) {
|
||||
return $this->responseError([], 'Database error: '.$exception->getMessage());
|
||||
} catch (Exception $e) {
|
||||
return $this->responseError([], $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function show(int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
return $this->responseSuccess($this->repo->getById($id), 'Floor has been fetched successfully.');
|
||||
} catch (Exception $e) {
|
||||
return $this->responseError([], $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function update(FloorUpdateRequest $request, int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
return $this->responseSuccess($this->repo->update($id, $request->all()), 'Floor has been updated successfully.');
|
||||
} catch (Exception $e) {
|
||||
return $this->responseError([], $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy(int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
return $this->responseSuccess($this->repo->delete($id), 'Floor has been deleted successfully.');
|
||||
} catch (Exception $e) {
|
||||
return $this->responseError([], $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Http\Controllers\API;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Exception;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Modules\Booking\Http\Requests\Hotel\HotelStoreRequest;
|
||||
use Modules\Booking\Http\Requests\Hotel\HotelUpdateRequest;
|
||||
use Modules\Booking\Repositories\HotelRepository;
|
||||
|
||||
class HotelController extends Controller
|
||||
{
|
||||
public function __construct(private HotelRepository $repo) {}
|
||||
|
||||
public function index(): JsonResponse
|
||||
{
|
||||
try {
|
||||
return $this->responseSuccess($this->repo->getAll(request()->all()), 'Hotel has been fetched successfully.');
|
||||
} catch (Exception $e) {
|
||||
return $this->responseError([], $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function store(HotelStoreRequest $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
return $this->responseSuccess($this->repo->create($request->all()), 'Hotel has been created successfully.');
|
||||
} catch (\Illuminate\Database\QueryException $exception) {
|
||||
return $this->responseError([], 'Database error: '.$exception->getMessage());
|
||||
} catch (Exception $e) {
|
||||
return $this->responseError([], $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function show(int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
return $this->responseSuccess($this->repo->getById($id), 'Hotel has been fetched successfully.');
|
||||
} catch (Exception $e) {
|
||||
return $this->responseError([], $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function update(HotelUpdateRequest $request, int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
return $this->responseSuccess($this->repo->update($id, $request->all()), 'Hotel has been updated successfully.');
|
||||
} catch (Exception $e) {
|
||||
return $this->responseError([], $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy(int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
return $this->responseSuccess($this->repo->delete($id), 'Hotel has been deleted successfully.');
|
||||
} catch (Exception $e) {
|
||||
return $this->responseError([], $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Http\Controllers\API;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Exception;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Modules\Booking\Http\Requests\RoomAvailability\RoomAvailabilityStoreRequest;
|
||||
use Modules\Booking\Http\Requests\RoomAvailability\RoomAvailabilityUpdateRequest;
|
||||
use Modules\Booking\Repositories\RoomAvailabilityRepository;
|
||||
|
||||
class RoomAvailabilityController extends Controller
|
||||
{
|
||||
public function __construct(private RoomAvailabilityRepository $repo) {}
|
||||
|
||||
public function index(): JsonResponse
|
||||
{
|
||||
try {
|
||||
return $this->responseSuccess($this->repo->getAll(request()->all()), 'RoomAvailability has been fetched successfully.');
|
||||
} catch (Exception $e) {
|
||||
return $this->responseError([], $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function store(RoomAvailabilityStoreRequest $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
return $this->responseSuccess($this->repo->create($request->all()), 'RoomAvailability has been created successfully.');
|
||||
} catch (\Illuminate\Database\QueryException $exception) {
|
||||
return $this->responseError([], 'Database error: '.$exception->getMessage());
|
||||
} catch (Exception $e) {
|
||||
return $this->responseError([], $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function show(int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
return $this->responseSuccess($this->repo->getById($id), 'RoomAvailability has been fetched successfully.');
|
||||
} catch (Exception $e) {
|
||||
return $this->responseError([], $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function update(RoomAvailabilityUpdateRequest $request, int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
return $this->responseSuccess($this->repo->update($id, $request->all()), 'RoomAvailability has been updated successfully.');
|
||||
} catch (Exception $e) {
|
||||
return $this->responseError([], $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy(int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
return $this->responseSuccess($this->repo->delete($id), 'RoomAvailability has been deleted successfully.');
|
||||
} catch (Exception $e) {
|
||||
return $this->responseError([], $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Http\Controllers\API;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Exception;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Modules\Booking\Http\Requests\RoomBlock\RoomBlockStoreRequest;
|
||||
use Modules\Booking\Http\Requests\RoomBlock\RoomBlockUpdateRequest;
|
||||
use Modules\Booking\Repositories\RoomBlockRepository;
|
||||
|
||||
class RoomBlockController extends Controller
|
||||
{
|
||||
public function __construct(private RoomBlockRepository $repo) {}
|
||||
|
||||
public function index(): JsonResponse
|
||||
{
|
||||
try {
|
||||
return $this->responseSuccess($this->repo->getAll(request()->all()), 'RoomBlock has been fetched successfully.');
|
||||
} catch (Exception $e) {
|
||||
return $this->responseError([], $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function store(RoomBlockStoreRequest $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
return $this->responseSuccess($this->repo->create($request->all()), 'RoomBlock has been created successfully.');
|
||||
} catch (\Illuminate\Database\QueryException $exception) {
|
||||
return $this->responseError([], 'Database error: '.$exception->getMessage());
|
||||
} catch (Exception $e) {
|
||||
return $this->responseError([], $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function show(int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
return $this->responseSuccess($this->repo->getById($id), 'RoomBlock has been fetched successfully.');
|
||||
} catch (Exception $e) {
|
||||
return $this->responseError([], $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function update(RoomBlockUpdateRequest $request, int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
return $this->responseSuccess($this->repo->update($id, $request->all()), 'RoomBlock has been updated successfully.');
|
||||
} catch (Exception $e) {
|
||||
return $this->responseError([], $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy(int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
return $this->responseSuccess($this->repo->delete($id), 'RoomBlock has been deleted successfully.');
|
||||
} catch (Exception $e) {
|
||||
return $this->responseError([], $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Http\Controllers\API;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Exception;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Modules\Booking\Http\Requests\Room\RoomStoreRequest;
|
||||
use Modules\Booking\Http\Requests\Room\RoomUpdateRequest;
|
||||
use Modules\Booking\Repositories\RoomRepository;
|
||||
|
||||
class RoomController extends Controller
|
||||
{
|
||||
public function __construct(private RoomRepository $repo) {}
|
||||
|
||||
public function index(): JsonResponse
|
||||
{
|
||||
try {
|
||||
return $this->responseSuccess($this->repo->getAll(request()->all()), 'Room has been fetched successfully.');
|
||||
} catch (Exception $e) {
|
||||
return $this->responseError([], $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function store(RoomStoreRequest $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
return $this->responseSuccess($this->repo->create($request->all()), 'Room has been created successfully.');
|
||||
} catch (\Illuminate\Database\QueryException $exception) {
|
||||
return $this->responseError([], 'Database error: '.$exception->getMessage());
|
||||
} catch (Exception $e) {
|
||||
return $this->responseError([], $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function show(int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
return $this->responseSuccess($this->repo->getById($id), 'Room has been fetched successfully.');
|
||||
} catch (Exception $e) {
|
||||
return $this->responseError([], $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function update(RoomUpdateRequest $request, int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
return $this->responseSuccess($this->repo->update($id, $request->all()), 'Room has been updated successfully.');
|
||||
} catch (Exception $e) {
|
||||
return $this->responseError([], $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy(int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
return $this->responseSuccess($this->repo->delete($id), 'Room has been deleted successfully.');
|
||||
} catch (Exception $e) {
|
||||
return $this->responseError([], $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Http\Controllers\API;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Exception;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Modules\Booking\Http\Requests\RoomType\RoomTypeStoreRequest;
|
||||
use Modules\Booking\Http\Requests\RoomType\RoomTypeUpdateRequest;
|
||||
use Modules\Booking\Repositories\RoomTypeRepository;
|
||||
|
||||
class RoomTypeController extends Controller
|
||||
{
|
||||
public function __construct(private RoomTypeRepository $repo) {}
|
||||
|
||||
public function index(): JsonResponse
|
||||
{
|
||||
try {
|
||||
return $this->responseSuccess($this->repo->getAll(request()->all()), 'RoomType has been fetched successfully.');
|
||||
} catch (Exception $e) {
|
||||
return $this->responseError([], $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function store(RoomTypeStoreRequest $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
return $this->responseSuccess($this->repo->create($request->all()), 'RoomType has been created successfully.');
|
||||
} catch (\Illuminate\Database\QueryException $exception) {
|
||||
return $this->responseError([], 'Database error: '.$exception->getMessage());
|
||||
} catch (Exception $e) {
|
||||
return $this->responseError([], $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function show(int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
return $this->responseSuccess($this->repo->getById($id), 'RoomType has been fetched successfully.');
|
||||
} catch (Exception $e) {
|
||||
return $this->responseError([], $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function update(RoomTypeUpdateRequest $request, int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
return $this->responseSuccess($this->repo->update($id, $request->all()), 'RoomType has been updated successfully.');
|
||||
} catch (Exception $e) {
|
||||
return $this->responseError([], $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy(int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
return $this->responseSuccess($this->repo->delete($id), 'RoomType has been deleted successfully.');
|
||||
} catch (Exception $e) {
|
||||
return $this->responseError([], $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Http\Requests\Booking;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class BookingStoreRequest extends FormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'hotel_id' => 'required|exists:hotels,id',
|
||||
'customer_id' => 'required|exists:customers,id',
|
||||
'check_in' => 'required|date',
|
||||
'check_out' => 'required|date|after_or_equal:check_in',
|
||||
'check_in_time' => 'nullable|date_format:H:i:s',
|
||||
'check_out_time' => 'nullable|date_format:H:i:s',
|
||||
'total_adults' => 'required|integer|min:1',
|
||||
'total_children' => 'required|integer|min:0',
|
||||
'subtotal_amount' => 'required|numeric|min:0',
|
||||
'tax_amount' => 'nullable|numeric|min:0',
|
||||
'discount_amount' => 'nullable|numeric|min:0',
|
||||
'total_amount' => 'required|numeric|min:0',
|
||||
'payment_status' => 'required|in:pending,paid,refunded',
|
||||
'payment_method' => 'nullable|in:cash,card,online,bank',
|
||||
'channel' => 'nullable|string',
|
||||
'status' => 'required|in:pending,confirmed,checked_in,checked_out,canceled',
|
||||
'remarks' => 'nullable|string',
|
||||
'booking_items' => 'required|array|min:1',
|
||||
'booking_items.*.room_id' => 'required|exists:rooms,id',
|
||||
'booking_items.*.adults' => 'required|integer|min:1',
|
||||
'booking_items.*.children' => 'required|integer|min:0',
|
||||
'booking_items.*.room_price' => 'required|numeric|min:0',
|
||||
'booking_items.*.nights' => 'required|integer|min:1',
|
||||
'booking_items.*.tax_amount' => 'nullable|numeric|min:0',
|
||||
'booking_items.*.total_amount' => 'required|numeric|min:0',
|
||||
'booking_items.*.status' => 'required|in:reserved,occupied,cleaning,completed,canceled',
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'booking_items.required' => 'At least one room must be added.',
|
||||
'booking_items.*.room_id.exists' => 'The selected room does not exist.',
|
||||
];
|
||||
}
|
||||
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Http\Requests\Booking;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class BookingUpdateRequest extends FormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'hotel_id' => 'nullable|exists:hotels,id',
|
||||
'customer_id' => 'nullable|exists:customers,id',
|
||||
'check_in' => 'nullable|date',
|
||||
'check_out' => 'nullable|date|after_or_equal:check_in',
|
||||
'check_in_time' => 'nullable|date_format:H:i:s',
|
||||
'check_out_time' => 'nullable|date_format:H:i:s',
|
||||
'total_adults' => 'nullable|integer|min:1',
|
||||
'total_children' => 'nullable|integer|min:0',
|
||||
'subtotal_amount' => 'nullable|numeric|min:0',
|
||||
'tax_amount' => 'nullable|numeric|min:0',
|
||||
'discount_amount' => 'nullable|numeric|min:0',
|
||||
'total_amount' => 'nullable|numeric|min:0',
|
||||
'payment_status' => 'nullable|in:pending,paid,refunded',
|
||||
'payment_method' => 'nullable|in:cash,card,online,bank',
|
||||
'channel' => 'nullable|string',
|
||||
'status' => 'nullable|in:pending,confirmed,checked_in,checked_out,canceled',
|
||||
'remarks' => 'nullable|string',
|
||||
'booking_items' => 'nullable|array|min:1',
|
||||
'booking_items.*.room_id' => 'required_with:booking_items|exists:rooms,id',
|
||||
'booking_items.*.adults' => 'required_with:booking_items|integer|min:1',
|
||||
'booking_items.*.children' => 'required_with:booking_items|integer|min:0',
|
||||
'booking_items.*.room_price' => 'required_with:booking_items|numeric|min:0',
|
||||
'booking_items.*.nights' => 'required_with:booking_items|integer|min:1',
|
||||
'booking_items.*.tax_amount' => 'nullable|numeric|min:0',
|
||||
'booking_items.*.total_amount' => 'required_with:booking_items|numeric|min:0',
|
||||
'booking_items.*.status' => 'required_with:booking_items|in:reserved,occupied,cleaning,completed,canceled',
|
||||
];
|
||||
}
|
||||
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Http\Requests\Booking;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class CustomerBookingStoreRequest extends FormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'hotel_id' => 'required|exists:hotels,id',
|
||||
// 'customer_id' => 'required|exists:customers,id',
|
||||
'check_in' => 'required|date',
|
||||
'check_out' => 'required|date|after_or_equal:check_in',
|
||||
'check_in_time' => 'nullable|date_format:H:i:s',
|
||||
'check_out_time' => 'nullable|date_format:H:i:s',
|
||||
'total_adults' => 'required|integer|min:1',
|
||||
'total_children' => 'required|integer|min:0',
|
||||
'subtotal_amount' => 'required|numeric|min:0',
|
||||
'tax_amount' => 'nullable|numeric|min:0',
|
||||
'discount_amount' => 'nullable|numeric|min:0',
|
||||
'total_amount' => 'required|numeric|min:0',
|
||||
'payment_status' => 'required|in:pending,paid,refunded',
|
||||
'payment_method' => 'nullable|in:cash,card,online,bank',
|
||||
'channel' => 'nullable|string',
|
||||
'status' => 'required|in:pending,confirmed,checked_in,checked_out,canceled',
|
||||
'remarks' => 'nullable|string',
|
||||
'booking_items' => 'required|array|min:1',
|
||||
'booking_items.*.room_id' => 'required|exists:rooms,id',
|
||||
'booking_items.*.adults' => 'required|integer|min:1',
|
||||
'booking_items.*.children' => 'required|integer|min:0',
|
||||
'booking_items.*.room_price' => 'required|numeric|min:0',
|
||||
'booking_items.*.nights' => 'required|integer|min:1',
|
||||
'booking_items.*.tax_amount' => 'nullable|numeric|min:0',
|
||||
'booking_items.*.total_amount' => 'required|numeric|min:0',
|
||||
'booking_items.*.status' => 'required|in:reserved,occupied,cleaning,completed,canceled',
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'booking_items.required' => 'At least one room must be added.',
|
||||
'booking_items.*.room_id.exists' => 'The selected room does not exist.',
|
||||
];
|
||||
}
|
||||
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Http\Requests\Floor;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class FloorStoreRequest extends FormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'hotel_id' => ['required', 'exists:hotels,id'],
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'level' => ['nullable', 'integer', 'min:-5', 'max:200'],
|
||||
'status' => ['nullable', 'integer', 'in:0,1'],
|
||||
];
|
||||
}
|
||||
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Http\Requests\Floor;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class FloorUpdateRequest extends FormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'hotel_id' => ['required', 'exists:hotels,id'],
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'level' => ['nullable', 'integer', 'min:-5', 'max:200'],
|
||||
'status' => ['nullable', 'integer', 'in:0,1'],
|
||||
];
|
||||
}
|
||||
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Http\Requests\Hotel;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class HotelStoreRequest extends FormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'location' => ['nullable', 'string', 'max:255'],
|
||||
'email' => ['nullable', 'email', 'max:255'],
|
||||
'phone' => ['nullable', 'string', 'max:30'],
|
||||
'description' => ['nullable', 'string'],
|
||||
'latitude' => ['nullable', 'numeric', 'between:-90,90'],
|
||||
'longitude' => ['nullable', 'numeric', 'between:-180,180'],
|
||||
'check_in_time' => ['nullable', 'date_format:H:i'],
|
||||
'check_out_time' => ['nullable', 'date_format:H:i'],
|
||||
'status' => ['required', 'integer', 'in:0,1'],
|
||||
];
|
||||
}
|
||||
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Http\Requests\Hotel;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class HotelUpdateRequest extends FormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'location' => ['nullable', 'string', 'max:255'],
|
||||
'email' => ['nullable', 'email', 'max:255'],
|
||||
'phone' => ['nullable', 'string', 'max:30'],
|
||||
'description' => ['nullable', 'string'],
|
||||
'latitude' => ['nullable', 'numeric', 'between:-90,90'],
|
||||
'longitude' => ['nullable', 'numeric', 'between:-180,180'],
|
||||
'check_in_time' => ['nullable', 'date_format:H:i'],
|
||||
'check_out_time' => ['nullable', 'date_format:H:i'],
|
||||
'status' => ['required', 'integer', 'in:0,1'],
|
||||
];
|
||||
}
|
||||
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Http\Requests\Room;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class RoomStoreRequest extends FormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'hotel_id' => ['required', 'integer', 'exists:hotels,id'],
|
||||
'floor_id' => ['required', 'integer', 'exists:floors,id'],
|
||||
'room_type_id' => ['required', 'integer', 'exists:room_types,id'],
|
||||
|
||||
'room_number' => ['required', 'string', 'max:50'],
|
||||
'room_code' => ['nullable', 'string', 'max:100'],
|
||||
'max_adults' => ['required', 'integer', 'min:1'],
|
||||
'max_children' => ['required', 'integer', 'min:0'],
|
||||
|
||||
'has_balcony' => ['boolean'],
|
||||
'has_bathtub' => ['boolean'],
|
||||
'view_type' => ['nullable', 'string', 'max:50'],
|
||||
'slug' => ['required', 'string', 'max:100'],
|
||||
|
||||
'image' => ['nullable', 'file', 'image'],
|
||||
'banner_image' => ['nullable', 'file', 'image'],
|
||||
'gallery_images' => ['nullable', 'array'],
|
||||
'gallery_images.*' => ['file', 'image'],
|
||||
|
||||
'description' => ['nullable', 'string'],
|
||||
|
||||
'regular_price' => ['required', 'numeric', 'min:0'],
|
||||
'offer_price' => ['nullable', 'numeric', 'min:0'],
|
||||
|
||||
'is_clean' => ['nullable', 'boolean'],
|
||||
'is_available' => ['nullable', 'boolean'],
|
||||
'status' => ['nullable', 'integer', 'in:0,1'],
|
||||
];
|
||||
}
|
||||
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Http\Requests\Room;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class RoomUpdateRequest extends FormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'hotel_id' => ['required', 'integer', 'exists:hotels,id'],
|
||||
'floor_id' => ['required', 'integer', 'exists:floors,id'],
|
||||
'room_type_id' => ['required', 'integer', 'exists:room_types,id'],
|
||||
|
||||
'room_number' => ['required', 'string', 'max:50'],
|
||||
'room_code' => ['nullable', 'string', 'max:100'],
|
||||
'max_adults' => ['required', 'integer', 'min:1'],
|
||||
'max_children' => ['required', 'integer', 'min:0'],
|
||||
|
||||
'has_balcony' => ['boolean'],
|
||||
'has_bathtub' => ['boolean'],
|
||||
'view_type' => ['nullable', 'string', 'max:50'],
|
||||
'slug' => ['required', 'string', 'max:100'],
|
||||
|
||||
'image' => ['nullable', 'file', 'image'],
|
||||
'banner_image' => ['nullable', 'file', 'image'],
|
||||
'gallery_images' => ['nullable', 'array'],
|
||||
'gallery_images.*' => ['file', 'image'],
|
||||
|
||||
'description' => ['nullable', 'string'],
|
||||
|
||||
'regular_price' => ['required', 'numeric', 'min:0'],
|
||||
'offer_price' => ['nullable', 'numeric', 'min:0'],
|
||||
|
||||
'is_clean' => ['nullable', 'boolean'],
|
||||
'is_available' => ['nullable', 'boolean'],
|
||||
'status' => ['nullable', 'integer', 'in:0,1'],
|
||||
];
|
||||
}
|
||||
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Http\Requests\RoomAvailability;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class RoomAvailabilityStoreRequest extends FormRequest
|
||||
{
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'hotel_id' => 'required|exists:hotels,id',
|
||||
'room_id' => 'required|exists:rooms,id',
|
||||
'total_inventory' => 'required|integer|min:1',
|
||||
'available_inventory' => 'required|integer|min:0',
|
||||
'date' => 'required|date',
|
||||
'is_available' => 'boolean',
|
||||
'is_closed_to_arrival' => 'boolean',
|
||||
'is_closed_to_departure' => 'boolean',
|
||||
'base_price' => 'nullable|numeric|min:0',
|
||||
'extra_adult_price' => 'nullable|numeric|min:0',
|
||||
'extra_child_price' => 'nullable|numeric|min:0',
|
||||
'min_stay' => 'integer|min:1',
|
||||
'max_stay' => 'nullable|integer|min:1',
|
||||
'blocked_by_booking_id' => 'nullable|exists:bookings,id',
|
||||
'block_type' => 'in:none,booking,maintenance,manual',
|
||||
'note' => 'nullable|string',
|
||||
'status' => 'integer|in:0,1',
|
||||
];
|
||||
}
|
||||
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Http\Requests\RoomAvailability;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class RoomAvailabilityUpdateRequest extends FormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'hotel_id' => 'required|exists:hotels,id',
|
||||
'room_id' => 'required|exists:rooms,id',
|
||||
'total_inventory' => 'required|integer|min:1',
|
||||
'available_inventory' => 'required|integer|min:0',
|
||||
'date' => 'required|date',
|
||||
'is_available' => 'boolean',
|
||||
'is_closed_to_arrival' => 'boolean',
|
||||
'is_closed_to_departure' => 'boolean',
|
||||
'base_price' => 'nullable|numeric|min:0',
|
||||
'extra_adult_price' => 'nullable|numeric|min:0',
|
||||
'extra_child_price' => 'nullable|numeric|min:0',
|
||||
'min_stay' => 'integer|min:1',
|
||||
'max_stay' => 'nullable|integer|min:1',
|
||||
'blocked_by_booking_id' => 'nullable|exists:bookings,id',
|
||||
'block_type' => 'in:none,booking,maintenance,manual',
|
||||
'note' => 'nullable|string',
|
||||
'status' => 'integer|in:0,1',
|
||||
];
|
||||
}
|
||||
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Http\Requests\RoomBlock;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class RoomBlockStoreRequest extends FormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'hotel_id' => 'nullable|exists:hotels,id',
|
||||
'room_id' => 'required|exists:rooms,id',
|
||||
'start_date' => 'required|date',
|
||||
'end_date' => 'required|date|after_or_equal:start_date',
|
||||
'start_time' => 'nullable|date_format:H:i:s',
|
||||
'end_time' => 'nullable|date_format:H:i:s|after:start_time',
|
||||
'block_type' => 'required|in:maintenance,deep_clean,renovation,owner_stay,event,other',
|
||||
'blocked_inventory' => 'required|integer|min:1',
|
||||
'priority' => 'integer|min:1',
|
||||
'reason' => 'nullable|string|max:255',
|
||||
'auto_release' => 'boolean',
|
||||
'created_by' => 'nullable|exists:users,id',
|
||||
'status' => 'nullable|in:0,1',
|
||||
];
|
||||
}
|
||||
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Http\Requests\RoomBlock;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class RoomBlockUpdateRequest extends FormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'hotel_id' => 'nullable|exists:hotels,id',
|
||||
'room_id' => 'required|exists:rooms,id',
|
||||
'start_date' => 'required|date',
|
||||
'end_date' => 'required|date|after_or_equal:start_date',
|
||||
'start_time' => 'nullable|date_format:H:i:s',
|
||||
'end_time' => 'nullable|date_format:H:i:s|after:start_time',
|
||||
'block_type' => 'required|in:maintenance,deep_clean,renovation,owner_stay,event,other',
|
||||
'blocked_inventory' => 'required|integer|min:1',
|
||||
'priority' => 'integer|min:1',
|
||||
'reason' => 'nullable|string|max:255',
|
||||
'auto_release' => 'boolean',
|
||||
'created_by' => 'nullable|exists:users,id',
|
||||
'status' => 'nullable|in:0,1',
|
||||
];
|
||||
}
|
||||
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Http\Requests\RoomType;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class RoomTypeStoreRequest extends FormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'hotel_id' => ['required', 'exists:hotels,id'],
|
||||
// Basic info
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'capacity' => ['required', 'integer', 'min:1', 'max:20'],
|
||||
// Room Features
|
||||
'beds' => ['required', 'integer', 'min:1', 'max:10'],
|
||||
'bed_type' => ['nullable', 'string', 'max:100'],
|
||||
'has_ac' => ['boolean'],
|
||||
'has_wifi' => ['boolean'],
|
||||
'has_breakfast' => ['boolean'],
|
||||
'is_refundable' => ['boolean'],
|
||||
// Pricing
|
||||
'base_price' => ['required', 'numeric', 'min:0'],
|
||||
'weekend_price' => ['nullable', 'numeric', 'min:0'],
|
||||
'extra_guest_price' => ['nullable', 'numeric', 'min:0'],
|
||||
// Description
|
||||
'description' => ['nullable', 'string'],
|
||||
// Status
|
||||
'status' => ['required', 'integer', 'in:0,1'],
|
||||
];
|
||||
}
|
||||
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Http\Requests\RoomType;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class RoomTypeUpdateRequest extends FormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'hotel_id' => ['required', 'exists:hotels,id'],
|
||||
// Basic info
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'capacity' => ['required', 'integer', 'min:1', 'max:20'],
|
||||
// Room Features
|
||||
'beds' => ['required', 'integer', 'min:1', 'max:10'],
|
||||
'bed_type' => ['nullable', 'string', 'max:100'],
|
||||
'has_ac' => ['boolean'],
|
||||
'has_wifi' => ['boolean'],
|
||||
'has_breakfast' => ['boolean'],
|
||||
'is_refundable' => ['boolean'],
|
||||
// Pricing
|
||||
'base_price' => ['required', 'numeric', 'min:0'],
|
||||
'weekend_price' => ['nullable', 'numeric', 'min:0'],
|
||||
'extra_guest_price' => ['nullable', 'numeric', 'min:0'],
|
||||
// Description
|
||||
'description' => ['nullable', 'string'],
|
||||
// Status
|
||||
'status' => ['required', 'integer', 'in:0,1'],
|
||||
];
|
||||
}
|
||||
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
93
public/restaurant/Modules/Booking/app/Models/Booking.php
Normal file
93
public/restaurant/Modules/Booking/app/Models/Booking.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Modules\Authentication\Models\User;
|
||||
use Modules\Restaurant\Models\Customer;
|
||||
|
||||
class Booking extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'restaurant_id',
|
||||
'hotel_id',
|
||||
'customer_id',
|
||||
'check_in',
|
||||
'check_out',
|
||||
'check_in_time',
|
||||
'check_out_time',
|
||||
'total_adults',
|
||||
'total_children',
|
||||
'subtotal_amount',
|
||||
'tax_amount',
|
||||
'discount_amount',
|
||||
'total_amount',
|
||||
'payment_status',
|
||||
'payment_method',
|
||||
'channel',
|
||||
'status',
|
||||
'canceled_at',
|
||||
'canceled_by',
|
||||
'remarks',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'deleted_at',
|
||||
];
|
||||
|
||||
/**
|
||||
* Casts
|
||||
*/
|
||||
protected $casts = [
|
||||
'restaurant_id' => 'integer',
|
||||
'hotel_id' => 'integer',
|
||||
'customer_id' => 'integer',
|
||||
|
||||
'check_in' => 'date',
|
||||
'check_out' => 'date',
|
||||
'check_in_time' => 'datetime:H:i',
|
||||
'check_out_time' => 'datetime:H:i',
|
||||
|
||||
'total_adults' => 'integer',
|
||||
'total_children' => 'integer',
|
||||
|
||||
'subtotal_amount' => 'decimal:2',
|
||||
'tax_amount' => 'decimal:2',
|
||||
'discount_amount' => 'decimal:2',
|
||||
'total_amount' => 'decimal:2',
|
||||
|
||||
'canceled_at' => 'datetime',
|
||||
'canceled_by' => 'integer',
|
||||
];
|
||||
|
||||
public const TABLE_NAME = 'bookings';
|
||||
|
||||
protected $table = self::TABLE_NAME;
|
||||
|
||||
public function hotel(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Hotel::class)->select('id', 'name');
|
||||
}
|
||||
|
||||
public function customer(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Customer::class)->select('id', 'name', 'phone', 'email', 'avatar');
|
||||
}
|
||||
|
||||
public function bookingItems(): HasMany
|
||||
{
|
||||
return $this->hasMany(BookingItem::class);
|
||||
}
|
||||
|
||||
public function rooms(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Room::class, 'booking_items');
|
||||
}
|
||||
|
||||
public function canceledBy(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'canceled_by');
|
||||
}
|
||||
}
|
||||
39
public/restaurant/Modules/Booking/app/Models/BookingItem.php
Normal file
39
public/restaurant/Modules/Booking/app/Models/BookingItem.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class BookingItem extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'restaurant_id',
|
||||
'booking_id',
|
||||
'room_id',
|
||||
'adults',
|
||||
'children',
|
||||
'room_price',
|
||||
'nights',
|
||||
'tax_amount',
|
||||
'total_amount',
|
||||
'status',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'deleted_at',
|
||||
];
|
||||
|
||||
public const TABLE_NAME = 'booking_items';
|
||||
|
||||
protected $table = self::TABLE_NAME;
|
||||
|
||||
public function booking(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Booking::class);
|
||||
}
|
||||
|
||||
public function room(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Room::class)->select('id', 'room_number', 'room_code', 'image');
|
||||
}
|
||||
}
|
||||
29
public/restaurant/Modules/Booking/app/Models/Floor.php
Normal file
29
public/restaurant/Modules/Booking/app/Models/Floor.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class Floor extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'restaurant_id',
|
||||
'hotel_id',
|
||||
'name',
|
||||
'level',
|
||||
'status',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'deleted_at',
|
||||
];
|
||||
|
||||
public const TABLE_NAME = 'floors';
|
||||
|
||||
protected $table = self::TABLE_NAME;
|
||||
|
||||
public function hotel(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Hotel::class)->select('id', 'name');
|
||||
}
|
||||
}
|
||||
65
public/restaurant/Modules/Booking/app/Models/Hotel.php
Normal file
65
public/restaurant/Modules/Booking/app/Models/Hotel.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Modules\Authentication\Models\Restaurant;
|
||||
|
||||
class Hotel extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'restaurant_id',
|
||||
'name',
|
||||
'location',
|
||||
'email',
|
||||
'phone',
|
||||
'description',
|
||||
'latitude',
|
||||
'longitude',
|
||||
'check_in_time',
|
||||
'check_out_time',
|
||||
'status',
|
||||
];
|
||||
|
||||
/**
|
||||
* Casts
|
||||
*/
|
||||
protected $casts = [
|
||||
'latitude' => 'decimal:7',
|
||||
'longitude' => 'decimal:7',
|
||||
'check_in_time' => 'datetime:H:i',
|
||||
'check_out_time' => 'datetime:H:i',
|
||||
'status' => 'integer',
|
||||
];
|
||||
|
||||
public const TABLE_NAME = 'hotels';
|
||||
|
||||
protected $table = self::TABLE_NAME;
|
||||
|
||||
public function restaurant(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Restaurant::class);
|
||||
}
|
||||
|
||||
public function floors(): HasMany
|
||||
{
|
||||
return $this->hasMany(Floor::class);
|
||||
}
|
||||
|
||||
public function roomTypes(): HasMany
|
||||
{
|
||||
return $this->hasMany(RoomType::class);
|
||||
}
|
||||
|
||||
public function rooms(): HasMany
|
||||
{
|
||||
return $this->hasMany(Room::class);
|
||||
}
|
||||
|
||||
public function bookings(): HasMany
|
||||
{
|
||||
return $this->hasMany(Booking::class);
|
||||
}
|
||||
}
|
||||
79
public/restaurant/Modules/Booking/app/Models/Room.php
Normal file
79
public/restaurant/Modules/Booking/app/Models/Room.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Room extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'restaurant_id',
|
||||
'hotel_id',
|
||||
'floor_id',
|
||||
'room_type_id',
|
||||
'room_number',
|
||||
'room_code',
|
||||
'slug',
|
||||
'max_adults',
|
||||
'max_children',
|
||||
'has_balcony',
|
||||
'has_bathtub',
|
||||
'view_type',
|
||||
'image',
|
||||
'banner_image',
|
||||
'gallery_images',
|
||||
'description',
|
||||
'regular_price',
|
||||
'offer_price',
|
||||
'is_clean',
|
||||
'is_available',
|
||||
'status',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'deleted_at',
|
||||
];
|
||||
|
||||
/**
|
||||
* Casts
|
||||
*/
|
||||
protected $casts = [
|
||||
'restaurant_id' => 'integer',
|
||||
'hotel_id' => 'integer',
|
||||
'floor_id' => 'integer',
|
||||
'room_type_id' => 'integer',
|
||||
'max_adults' => 'integer',
|
||||
'max_children' => 'integer',
|
||||
'has_balcony' => 'boolean',
|
||||
'has_bathtub' => 'boolean',
|
||||
'gallery_images' => 'array',
|
||||
'is_clean' => 'boolean',
|
||||
'is_available' => 'boolean',
|
||||
'status' => 'integer',
|
||||
];
|
||||
|
||||
public const TABLE_NAME = 'rooms';
|
||||
|
||||
protected $table = self::TABLE_NAME;
|
||||
|
||||
public function hotel(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Hotel::class);
|
||||
}
|
||||
|
||||
public function floor(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Floor::class);
|
||||
}
|
||||
|
||||
public function roomType(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(RoomType::class);
|
||||
}
|
||||
|
||||
public function bookings(): HasMany
|
||||
{
|
||||
return $this->hasMany(BookingItem::class, 'room_id');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class RoomAvailability extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'restaurant_id',
|
||||
'hotel_id',
|
||||
'room_id',
|
||||
'date',
|
||||
|
||||
'total_inventory',
|
||||
'available_inventory',
|
||||
|
||||
'is_available',
|
||||
'is_closed_to_arrival',
|
||||
'is_closed_to_departure',
|
||||
|
||||
'base_price',
|
||||
'extra_adult_price',
|
||||
'extra_child_price',
|
||||
|
||||
'min_stay',
|
||||
'max_stay',
|
||||
|
||||
'blocked_by_booking_id',
|
||||
'block_type',
|
||||
'note',
|
||||
|
||||
'status',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'deleted_at',
|
||||
];
|
||||
|
||||
public const TABLE_NAME = 'room_availabilities';
|
||||
|
||||
protected $table = self::TABLE_NAME;
|
||||
|
||||
public function hotel(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Hotel::class);
|
||||
}
|
||||
|
||||
public function room(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Room::class);
|
||||
}
|
||||
|
||||
public function blockedByBooking(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Booking::class, 'blocked_by_booking_id');
|
||||
}
|
||||
}
|
||||
49
public/restaurant/Modules/Booking/app/Models/RoomBlock.php
Normal file
49
public/restaurant/Modules/Booking/app/Models/RoomBlock.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Modules\Authentication\Models\User;
|
||||
|
||||
class RoomBlock extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'restaurant_id',
|
||||
'hotel_id',
|
||||
'room_id',
|
||||
'start_date',
|
||||
'end_date',
|
||||
'start_time',
|
||||
'end_time',
|
||||
'block_type',
|
||||
'blocked_inventory',
|
||||
'priority',
|
||||
'reason',
|
||||
'auto_release',
|
||||
'created_by',
|
||||
'status',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'deleted_at',
|
||||
];
|
||||
|
||||
public const TABLE_NAME = 'room_blocks';
|
||||
|
||||
protected $table = self::TABLE_NAME;
|
||||
|
||||
public function hotel(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Hotel::class);
|
||||
}
|
||||
|
||||
public function room(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Room::class);
|
||||
}
|
||||
|
||||
public function creator(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by');
|
||||
}
|
||||
}
|
||||
36
public/restaurant/Modules/Booking/app/Models/RoomPrice.php
Normal file
36
public/restaurant/Modules/Booking/app/Models/RoomPrice.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class RoomPrice extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'restaurant_id',
|
||||
'room_id',
|
||||
'start_date',
|
||||
'end_date',
|
||||
'day_type',
|
||||
'price',
|
||||
'priority',
|
||||
'is_active',
|
||||
];
|
||||
|
||||
/**
|
||||
* Casts
|
||||
*/
|
||||
protected $casts = [
|
||||
'restaurant_id' => 'integer',
|
||||
'room_id' => 'integer',
|
||||
'start_date' => 'date',
|
||||
'end_date' => 'date',
|
||||
'price' => 'decimal:2',
|
||||
'priority' => 'integer',
|
||||
'is_active' => 'boolean',
|
||||
];
|
||||
|
||||
public const TABLE_NAME = 'room_prices';
|
||||
|
||||
protected $table = self::TABLE_NAME;
|
||||
}
|
||||
63
public/restaurant/Modules/Booking/app/Models/RoomType.php
Normal file
63
public/restaurant/Modules/Booking/app/Models/RoomType.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class RoomType extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'restaurant_id',
|
||||
'hotel_id',
|
||||
'name',
|
||||
'capacity',
|
||||
'beds',
|
||||
'bed_type',
|
||||
'has_ac',
|
||||
'has_wifi',
|
||||
'has_breakfast',
|
||||
'is_refundable',
|
||||
'base_price',
|
||||
'weekend_price',
|
||||
'extra_guest_price',
|
||||
'description',
|
||||
'status',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'deleted_at',
|
||||
];
|
||||
|
||||
/**
|
||||
* Casts
|
||||
*/
|
||||
protected $casts = [
|
||||
'restaurant_id' => 'integer',
|
||||
'hotel_id' => 'integer',
|
||||
'capacity' => 'integer',
|
||||
'beds' => 'integer',
|
||||
'has_ac' => 'boolean',
|
||||
'has_wifi' => 'boolean',
|
||||
'has_breakfast' => 'boolean',
|
||||
'is_refundable' => 'boolean',
|
||||
'base_price' => 'decimal:2',
|
||||
'weekend_price' => 'decimal:2',
|
||||
'extra_guest_price' => 'decimal:2',
|
||||
'status' => 'integer',
|
||||
];
|
||||
|
||||
public const TABLE_NAME = 'room_types';
|
||||
|
||||
protected $table = self::TABLE_NAME;
|
||||
|
||||
public function hotel(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Hotel::class);
|
||||
}
|
||||
|
||||
public function rooms(): HasMany
|
||||
{
|
||||
return $this->hasMany(Room::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Providers;
|
||||
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Nwidart\Modules\Traits\PathNamespace;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
|
||||
class BookingServiceProvider extends ServiceProvider
|
||||
{
|
||||
use PathNamespace;
|
||||
|
||||
protected string $name = 'Booking';
|
||||
|
||||
protected string $nameLower = 'booking';
|
||||
|
||||
/**
|
||||
* Boot the application events.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
$this->registerCommands();
|
||||
$this->registerCommandSchedules();
|
||||
$this->registerTranslations();
|
||||
$this->registerConfig();
|
||||
$this->registerViews();
|
||||
$this->loadMigrationsFrom(module_path($this->name, 'database/migrations'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the service provider.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
$this->app->register(EventServiceProvider::class);
|
||||
$this->app->register(RouteServiceProvider::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register commands in the format of Command::class
|
||||
*/
|
||||
protected function registerCommands(): void
|
||||
{
|
||||
// $this->commands([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register command Schedules.
|
||||
*/
|
||||
protected function registerCommandSchedules(): void
|
||||
{
|
||||
// $this->app->booted(function () {
|
||||
// $schedule = $this->app->make(Schedule::class);
|
||||
// $schedule->command('inspire')->hourly();
|
||||
// });
|
||||
}
|
||||
|
||||
/**
|
||||
* Register translations.
|
||||
*/
|
||||
public function registerTranslations(): void
|
||||
{
|
||||
$langPath = resource_path('lang/modules/'.$this->nameLower);
|
||||
|
||||
if (is_dir($langPath)) {
|
||||
$this->loadTranslationsFrom($langPath, $this->nameLower);
|
||||
$this->loadJsonTranslationsFrom($langPath);
|
||||
} else {
|
||||
$this->loadTranslationsFrom(module_path($this->name, 'lang'), $this->nameLower);
|
||||
$this->loadJsonTranslationsFrom(module_path($this->name, 'lang'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register config.
|
||||
*/
|
||||
protected function registerConfig(): void
|
||||
{
|
||||
$configPath = module_path($this->name, config('modules.paths.generator.config.path'));
|
||||
|
||||
if (is_dir($configPath)) {
|
||||
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($configPath));
|
||||
|
||||
foreach ($iterator as $file) {
|
||||
if ($file->isFile() && $file->getExtension() === 'php') {
|
||||
$config = str_replace($configPath.DIRECTORY_SEPARATOR, '', $file->getPathname());
|
||||
$config_key = str_replace([DIRECTORY_SEPARATOR, '.php'], ['.', ''], $config);
|
||||
$segments = explode('.', $this->nameLower.'.'.$config_key);
|
||||
|
||||
// Remove duplicated adjacent segments
|
||||
$normalized = [];
|
||||
foreach ($segments as $segment) {
|
||||
if (end($normalized) !== $segment) {
|
||||
$normalized[] = $segment;
|
||||
}
|
||||
}
|
||||
|
||||
$key = ($config === 'config.php') ? $this->nameLower : implode('.', $normalized);
|
||||
|
||||
$this->publishes([$file->getPathname() => config_path($config)], 'config');
|
||||
$this->merge_config_from($file->getPathname(), $key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge config from the given path recursively.
|
||||
*/
|
||||
protected function merge_config_from(string $path, string $key): void
|
||||
{
|
||||
$existing = config($key, []);
|
||||
$module_config = require $path;
|
||||
|
||||
config([$key => array_replace_recursive($existing, $module_config)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register views.
|
||||
*/
|
||||
public function registerViews(): void
|
||||
{
|
||||
$viewPath = resource_path('views/modules/'.$this->nameLower);
|
||||
$sourcePath = module_path($this->name, 'resources/views');
|
||||
|
||||
$this->publishes([$sourcePath => $viewPath], ['views', $this->nameLower.'-module-views']);
|
||||
|
||||
$this->loadViewsFrom(array_merge($this->getPublishableViewPaths(), [$sourcePath]), $this->nameLower);
|
||||
|
||||
Blade::componentNamespace(config('modules.namespace').'\\'.$this->name.'\\View\\Components', $this->nameLower);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the services provided by the provider.
|
||||
*/
|
||||
public function provides(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
private function getPublishableViewPaths(): array
|
||||
{
|
||||
$paths = [];
|
||||
foreach (config('view.paths') as $path) {
|
||||
if (is_dir($path.'/modules/'.$this->nameLower)) {
|
||||
$paths[] = $path.'/modules/'.$this->nameLower;
|
||||
}
|
||||
}
|
||||
|
||||
return $paths;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Providers;
|
||||
|
||||
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
|
||||
|
||||
class EventServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* The event handler mappings for the application.
|
||||
*
|
||||
* @var array<string, array<int, string>>
|
||||
*/
|
||||
protected $listen = [];
|
||||
|
||||
/**
|
||||
* Indicates if events should be discovered.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected static $shouldDiscoverEvents = true;
|
||||
|
||||
/**
|
||||
* Configure the proper event listeners for email verification.
|
||||
*/
|
||||
protected function configureEmailVerification(): void {}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Providers;
|
||||
|
||||
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
class RouteServiceProvider extends ServiceProvider
|
||||
{
|
||||
protected string $name = 'Booking';
|
||||
|
||||
/**
|
||||
* Called before routes are registered.
|
||||
*
|
||||
* Register any model bindings or pattern based filters.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
parent::boot();
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the routes for the application.
|
||||
*/
|
||||
public function map(): void
|
||||
{
|
||||
$this->mapApiRoutes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the "api" routes for the application.
|
||||
*
|
||||
* These routes are typically stateless.
|
||||
*/
|
||||
protected function mapApiRoutes(): void
|
||||
{
|
||||
Route::middleware('api')->prefix('api')->name('api.')->group(module_path($this->name, '/routes/api.php'));
|
||||
}
|
||||
}
|
||||
30
public/restaurant/Modules/Booking/composer.json
Normal file
30
public/restaurant/Modules/Booking/composer.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "nwidart/booking",
|
||||
"description": "",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Widart",
|
||||
"email": "n.widart@gmail.com"
|
||||
}
|
||||
],
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [],
|
||||
"aliases": {
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Modules\\Booking\\": "app/",
|
||||
"Modules\\Booking\\Database\\Factories\\": "database/factories/",
|
||||
"Modules\\Booking\\Database\\Seeders\\": "database/seeders/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Modules\\Booking\\Tests\\": "tests/"
|
||||
}
|
||||
}
|
||||
}
|
||||
0
public/restaurant/Modules/Booking/config/.gitkeep
Normal file
0
public/restaurant/Modules/Booking/config/.gitkeep
Normal file
5
public/restaurant/Modules/Booking/config/config.php
Normal file
5
public/restaurant/Modules/Booking/config/config.php
Normal file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'name' => 'Booking',
|
||||
];
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('hotels', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
// If a hotel belongs to a restaurant/business group
|
||||
$table->foreignId('restaurant_id')
|
||||
->nullable()
|
||||
->constrained('restaurants')
|
||||
->nullOnDelete();
|
||||
|
||||
$table->string('name');
|
||||
$table->string('location')->nullable();
|
||||
$table->string('email')->nullable();
|
||||
$table->string('phone')->nullable();
|
||||
$table->text('description')->nullable();
|
||||
|
||||
$table->decimal('latitude', 10, 7)->nullable();
|
||||
$table->decimal('longitude', 10, 7)->nullable();
|
||||
|
||||
$table->time('check_in_time')->default('14:00')->comment('Standard hotel check-in time');
|
||||
$table->time('check_out_time')->default('12:00')->comment('Standard hotel check-out time');
|
||||
|
||||
// Status: 1 = Active, 0 = Inactive (more standard)
|
||||
$table->smallInteger('status')->default(1)->comment('1=Active, 2=InActive');
|
||||
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('hotels');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('floors', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('restaurant_id')->nullable();
|
||||
$table->unsignedBigInteger('hotel_id');
|
||||
$table->string('name'); // e.g., Lobby, 1st Floor
|
||||
$table->integer('level')->nullable(); // optional floor number
|
||||
$table->smallInteger('status')->default(1)->comment('1=Active, 2=InActive');
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('floors');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('room_types', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
// Optional link if restaurants own hotels
|
||||
$table->foreignId('restaurant_id')
|
||||
->nullable()
|
||||
->constrained('restaurants')
|
||||
->nullOnDelete();
|
||||
|
||||
// Each room type belongs to a hotel
|
||||
$table->foreignId('hotel_id')
|
||||
->constrained('hotels')
|
||||
->cascadeOnDelete();
|
||||
|
||||
// Basic Info
|
||||
$table->string('name'); // e.g., Deluxe, Suite, Standard
|
||||
$table->integer('capacity')->default(1); // number of guests
|
||||
|
||||
// Room Features (real-world usage)
|
||||
$table->integer('beds')->default(1); // number of beds
|
||||
$table->string('bed_type')->nullable(); // Queen, King, Twin, etc.
|
||||
$table->boolean('has_ac')->default(true);
|
||||
$table->boolean('has_wifi')->default(true);
|
||||
$table->boolean('has_breakfast')->default(false);
|
||||
$table->boolean('is_refundable')->default(true);
|
||||
|
||||
// Pricing (standard hotel structure)
|
||||
$table->decimal('base_price', 10, 2); // base per-night price
|
||||
$table->decimal('weekend_price', 10, 2)->nullable(); // optional weekend price
|
||||
$table->decimal('extra_guest_price', 10, 2)->nullable();
|
||||
|
||||
// Optional description for UI
|
||||
$table->text('description')->nullable();
|
||||
|
||||
// Availability
|
||||
$table->smallInteger('status')->default(1)->comment('1=Active, 2=InActive');
|
||||
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('room_types');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('rooms', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
// If the system belongs to a restaurant group
|
||||
$table->foreignId('restaurant_id')
|
||||
->nullable()
|
||||
->constrained('restaurants')
|
||||
->nullOnDelete();
|
||||
|
||||
// Main hotel reference
|
||||
$table->foreignId('hotel_id')
|
||||
->constrained('hotels')
|
||||
->cascadeOnDelete();
|
||||
|
||||
// Floor reference
|
||||
$table->foreignId('floor_id')
|
||||
->constrained('floors')
|
||||
->cascadeOnDelete();
|
||||
|
||||
// Room type reference
|
||||
$table->foreignId('room_type_id')
|
||||
->constrained('room_types')
|
||||
->cascadeOnDelete();
|
||||
|
||||
// Room Info
|
||||
$table->string('room_number'); // e.g. 101, 502A
|
||||
$table->string('room_code')->nullable(); // unique internal tracking code (optional)
|
||||
$table->integer('max_adults')->default(2);
|
||||
$table->integer('max_children')->default(0);
|
||||
|
||||
// Features
|
||||
$table->boolean('has_balcony')->default(false);
|
||||
$table->boolean('has_bathtub')->default(false);
|
||||
|
||||
// View type: sea, city, garden, mountain, pool, etc.
|
||||
$table->string('view_type')->nullable();
|
||||
$table->string('slug')->nullable();
|
||||
|
||||
// Media / Images
|
||||
$table->string('image')->nullable(); // main display image
|
||||
$table->string('banner_image')->nullable(); // top banner image
|
||||
$table->json('gallery_images')->nullable(); // multiple images stored as JSON
|
||||
|
||||
// Extra description
|
||||
$table->text('description')->nullable();
|
||||
|
||||
// Pricing
|
||||
$table->decimal('regular_price', 10, 2)->nullable()->comment('Base room price');
|
||||
$table->decimal('offer_price', 10, 2)->nullable()->comment('Discounted price if any');
|
||||
|
||||
// Room Availability
|
||||
$table->boolean('is_clean')->default(true); // housekeeping status
|
||||
$table->boolean('is_available')->default(true); // available or blocked
|
||||
|
||||
// Active/Inactive
|
||||
$table->smallInteger('status')->default(1)->comment('1=Active, 2=InActive');
|
||||
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
|
||||
$table->unique(['hotel_id', 'room_number'], 'hotel_room_number_unique');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('rooms');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('room_prices', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
// Optional if restaurants own hotels
|
||||
$table->foreignId('restaurant_id')
|
||||
->nullable()
|
||||
->constrained('restaurants')
|
||||
->nullOnDelete();
|
||||
|
||||
$table->foreignId('room_id')->constrained('rooms')->cascadeOnDelete();
|
||||
|
||||
// Pricing period
|
||||
$table->date('start_date');
|
||||
$table->date('end_date');
|
||||
|
||||
// Day category: normal, weekend, holiday, peak, event
|
||||
$table->enum('day_type', ['normal', 'weekend', 'holiday', 'event', 'peak'])
|
||||
->default('normal');
|
||||
|
||||
// Price for that day/date range
|
||||
$table->decimal('price', 10, 2);
|
||||
|
||||
// For overlapping pricing rules → higher priority wins
|
||||
$table->integer('priority')->default(1);
|
||||
|
||||
// Status
|
||||
$table->boolean('is_active')
|
||||
->default(true)
|
||||
->comment('true = Active, false = Inactive');
|
||||
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('room_prices');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('bookings', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
// Optional restaurant group owner
|
||||
$table->foreignId('restaurant_id')
|
||||
->nullable()
|
||||
->constrained('restaurants')
|
||||
->nullOnDelete();
|
||||
|
||||
$table->foreignId('hotel_id')
|
||||
->constrained('hotels')
|
||||
->cascadeOnDelete();
|
||||
|
||||
// Customer (guest)
|
||||
$table->foreignId('customer_id')
|
||||
->constrained('customers')
|
||||
->cascadeOnDelete();
|
||||
|
||||
// Stay period
|
||||
$table->date('check_in');
|
||||
$table->date('check_out');
|
||||
|
||||
// optionally store times (if check_in/out only date previously)
|
||||
$table->time('check_in_time')->nullable();
|
||||
$table->time('check_out_time')->nullable();
|
||||
|
||||
// Guest breakdown
|
||||
$table->integer('total_adults')->default(1);
|
||||
$table->integer('total_children')->default(0);
|
||||
|
||||
// Pricing
|
||||
$table->decimal('subtotal_amount', 10, 2)->default(0); // rooms only
|
||||
$table->decimal('tax_amount', 10, 2)->default(0);
|
||||
$table->decimal('discount_amount', 10, 2)->default(0);
|
||||
$table->decimal('total_amount', 10, 2)->default(0);
|
||||
|
||||
// Payment information
|
||||
$table->enum('payment_status', ['pending', 'paid', 'refunded'])->default('pending');
|
||||
$table->enum('payment_method', ['cash', 'card', 'online', 'bank'])->nullable();
|
||||
|
||||
// source/channel (website, ota, walkin)
|
||||
$table->string('channel')->nullable();
|
||||
|
||||
// Booking status
|
||||
$table->enum('status', ['pending', 'confirmed', 'checked_in', 'checked_out', 'canceled'])
|
||||
->default('pending');
|
||||
|
||||
// Cancellation tracking
|
||||
$table->timestamp('canceled_at')->nullable();
|
||||
$table->foreignId('canceled_by')->nullable()->constrained('users');
|
||||
|
||||
// Notes
|
||||
$table->text('remarks')->nullable();
|
||||
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
|
||||
// small optimization index
|
||||
$table->index(['hotel_id', 'status']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('bookings');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('booking_items', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
$table->foreignId('restaurant_id')
|
||||
->nullable()
|
||||
->constrained('restaurants')
|
||||
->nullOnDelete();
|
||||
|
||||
$table->foreignId('booking_id')
|
||||
->constrained('bookings')
|
||||
->cascadeOnDelete();
|
||||
|
||||
$table->foreignId('room_id')
|
||||
->constrained('rooms')
|
||||
->cascadeOnDelete();
|
||||
|
||||
// Guest count for this room
|
||||
$table->integer('adults')->default(1);
|
||||
$table->integer('children')->default(0);
|
||||
|
||||
// Price assigned to this room (per night * nights)
|
||||
$table->decimal('room_price', 10, 2); // final price for stay (no tax)
|
||||
|
||||
// Calculated nights
|
||||
$table->integer('nights')->default(1);
|
||||
|
||||
$table->decimal('tax_amount', 10, 2)->default(0);
|
||||
$table->decimal('total_amount', 10, 2)->default(0);
|
||||
|
||||
// Housekeeping status (optional)
|
||||
$table->enum('status', ['reserved', 'occupied', 'cleaning', 'completed', 'canceled'])
|
||||
->default('reserved');
|
||||
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('booking_items');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('room_availabilities', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
$table->unsignedBigInteger('restaurant_id')->nullable();
|
||||
|
||||
$table->foreignId('hotel_id')
|
||||
->constrained('hotels')
|
||||
->cascadeOnDelete();
|
||||
|
||||
$table->foreignId('room_id')
|
||||
->constrained('rooms')
|
||||
->cascadeOnDelete();
|
||||
|
||||
// Support multiple room inventory (e.g., 10 deluxe rooms)
|
||||
$table->integer('total_inventory')->default(1);
|
||||
$table->integer('available_inventory')->default(1);
|
||||
|
||||
// Date (1 row per room per date)
|
||||
$table->date('date');
|
||||
|
||||
// Availability flags
|
||||
$table->boolean('is_available')->default(true);
|
||||
$table->boolean('is_closed_to_arrival')->default(false);
|
||||
$table->boolean('is_closed_to_departure')->default(false);
|
||||
|
||||
// Pricing for that day (smart dynamic rate support)
|
||||
$table->decimal('base_price', 10, 2)->nullable();
|
||||
$table->decimal('extra_adult_price', 10, 2)->nullable();
|
||||
$table->decimal('extra_child_price', 10, 2)->nullable();
|
||||
|
||||
// Minimum stay rules
|
||||
$table->integer('min_stay')->default(1);
|
||||
$table->integer('max_stay')->nullable(); // optional
|
||||
|
||||
// Reference to what blocked the inventory
|
||||
$table->foreignId('blocked_by_booking_id')
|
||||
->nullable()
|
||||
->constrained('bookings')
|
||||
->nullOnDelete();
|
||||
|
||||
// Maintenance / admin block info
|
||||
$table->enum('block_type', ['none', 'booking', 'maintenance', 'manual'])->default('none');
|
||||
$table->text('note')->nullable();
|
||||
|
||||
// Status
|
||||
$table->smallInteger('status')->default(1)->comment('1=Active, 2=InActive');
|
||||
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
|
||||
// Ensures 1 row per room per date
|
||||
$table->unique(['room_id', 'date'], 'room_date_unique');
|
||||
|
||||
$table->index(['hotel_id', 'date']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('room_availabilities');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('room_blocks', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
$table->unsignedBigInteger('restaurant_id')->nullable();
|
||||
|
||||
$table->foreignId('hotel_id')
|
||||
->nullable()
|
||||
->constrained('hotels')
|
||||
->cascadeOnDelete();
|
||||
|
||||
$table->foreignId('room_id')
|
||||
->constrained('rooms')
|
||||
->cascadeOnDelete();
|
||||
|
||||
// Flexible blocking
|
||||
$table->date('start_date');
|
||||
$table->date('end_date');
|
||||
|
||||
// Partial day support
|
||||
$table->time('start_time')->nullable(); // null = full day block
|
||||
$table->time('end_time')->nullable();
|
||||
|
||||
// New: Block type
|
||||
$table->enum('block_type', ['maintenance', 'deep_clean', 'renovation', 'owner_stay', 'event', 'other'])
|
||||
->default('maintenance');
|
||||
|
||||
// Multi-inventory rooms
|
||||
$table->integer('blocked_inventory')->default(1); // e.g., block 1 of 5 room-inventory
|
||||
|
||||
// Priority: higher wins (OTA, admin, system blocks)
|
||||
$table->integer('priority')->default(1);
|
||||
|
||||
// Reason for staff logs
|
||||
$table->string('reason')->nullable();
|
||||
|
||||
// Auto-expire block (optional)
|
||||
$table->boolean('auto_release')->default(true);
|
||||
|
||||
// Who created
|
||||
$table->foreignId('created_by')
|
||||
->nullable()
|
||||
->constrained('users')
|
||||
->nullOnDelete();
|
||||
|
||||
// Active/Inactive
|
||||
$table->smallInteger('status')->default(1)->comment('1=Active, 2=Inactive');
|
||||
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
|
||||
// Useful for availability engine
|
||||
$table->index(['room_id', 'start_date', 'end_date']);
|
||||
$table->index(['hotel_id', 'block_type']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('room_blocks');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class BookingDatabaseSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
$this->call([
|
||||
HotelTableSeeder::class,
|
||||
FloorTableSeeder::class,
|
||||
RoomTypeTableSeeder::class,
|
||||
RoomTableSeeder::class,
|
||||
RoomAvailabilityTableSeeder::class,
|
||||
RoomBlockTableSeeder::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Database\Seeders;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class BookingTableSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
// Sample bookings
|
||||
$bookings = [
|
||||
[
|
||||
'restaurant_id' => 1,
|
||||
'hotel_id' => 1,
|
||||
'customer_id' => 2,
|
||||
'check_in' => Carbon::now()->addDays(2)->format('Y-m-d'),
|
||||
'check_out' => Carbon::now()->addDays(5)->format('Y-m-d'),
|
||||
'check_in_time' => '14:00:00',
|
||||
'check_out_time' => '12:00:00',
|
||||
'total_adults' => 2,
|
||||
'total_children' => 1,
|
||||
'subtotal_amount' => 360.00,
|
||||
'tax_amount' => 36.00,
|
||||
'discount_amount' => 20.00,
|
||||
'total_amount' => 376.00,
|
||||
'payment_status' => 'pending',
|
||||
'payment_method' => 'cash',
|
||||
'channel' => 'website',
|
||||
'status' => 'confirmed',
|
||||
'remarks' => 'VIP customer',
|
||||
'created_at' => Carbon::now(),
|
||||
'updated_at' => Carbon::now(),
|
||||
],
|
||||
[
|
||||
'restaurant_id' => 1,
|
||||
'hotel_id' => 1,
|
||||
'customer_id' => 3,
|
||||
'check_in' => Carbon::now()->addDays(3)->format('Y-m-d'),
|
||||
'check_out' => Carbon::now()->addDays(4)->format('Y-m-d'),
|
||||
'check_in_time' => '15:00:00',
|
||||
'check_out_time' => '11:00:00',
|
||||
'total_adults' => 1,
|
||||
'total_children' => 0,
|
||||
'subtotal_amount' => 120.00,
|
||||
'tax_amount' => 12.00,
|
||||
'discount_amount' => 0,
|
||||
'total_amount' => 132.00,
|
||||
'payment_status' => 'pending',
|
||||
'payment_method' => 'card',
|
||||
'channel' => 'walkin',
|
||||
'status' => 'pending',
|
||||
'remarks' => 'First-time guest',
|
||||
'created_at' => Carbon::now(),
|
||||
'updated_at' => Carbon::now(),
|
||||
],
|
||||
];
|
||||
|
||||
// Insert bookings and get inserted IDs
|
||||
foreach ($bookings as $bookingData) {
|
||||
$bookingId = DB::table('bookings')->insertGetId($bookingData);
|
||||
|
||||
// Sample booking items (rooms)
|
||||
$bookingItems = [
|
||||
[
|
||||
'restaurant_id' => $bookingData['restaurant_id'],
|
||||
'booking_id' => $bookingId,
|
||||
'room_id' => 1,
|
||||
'adults' => 2,
|
||||
'children' => 1,
|
||||
'room_price' => 120.00 * 3, // nights * per-night price
|
||||
'nights' => 3,
|
||||
'tax_amount' => 36.00,
|
||||
'total_amount' => 156.00, // room_price + tax
|
||||
'status' => 'reserved',
|
||||
'created_at' => Carbon::now(),
|
||||
'updated_at' => Carbon::now(),
|
||||
],
|
||||
[
|
||||
'restaurant_id' => $bookingData['restaurant_id'],
|
||||
'booking_id' => $bookingId,
|
||||
'room_id' => 2,
|
||||
'adults' => 0,
|
||||
'children' => 0,
|
||||
'room_price' => 0, // no extra room for first booking
|
||||
'nights' => 0,
|
||||
'tax_amount' => 0,
|
||||
'total_amount' => 0,
|
||||
'status' => 'reserved',
|
||||
'created_at' => Carbon::now(),
|
||||
'updated_at' => Carbon::now(),
|
||||
],
|
||||
];
|
||||
|
||||
DB::table('booking_items')->insert($bookingItems);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Database\Seeders;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class FloorTableSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
$floors = [
|
||||
[
|
||||
'restaurant_id' => 1,
|
||||
'hotel_id' => 1,
|
||||
'name' => 'Lobby',
|
||||
'level' => 0,
|
||||
'status' => 1,
|
||||
'created_at' => Carbon::now(),
|
||||
'updated_at' => Carbon::now(),
|
||||
],
|
||||
[
|
||||
'restaurant_id' => 1,
|
||||
'hotel_id' => 1,
|
||||
'name' => '1st Floor',
|
||||
'level' => 1,
|
||||
'status' => 1,
|
||||
'created_at' => Carbon::now(),
|
||||
'updated_at' => Carbon::now(),
|
||||
],
|
||||
[
|
||||
'restaurant_id' => 1,
|
||||
'hotel_id' => 1,
|
||||
'name' => '2nd Floor',
|
||||
'level' => 2,
|
||||
'status' => 1,
|
||||
'created_at' => Carbon::now(),
|
||||
'updated_at' => Carbon::now(),
|
||||
],
|
||||
[
|
||||
'restaurant_id' => 1,
|
||||
'hotel_id' => 2,
|
||||
'name' => 'Lobby',
|
||||
'level' => 0,
|
||||
'status' => 1,
|
||||
'created_at' => Carbon::now(),
|
||||
'updated_at' => Carbon::now(),
|
||||
],
|
||||
[
|
||||
'restaurant_id' => 1,
|
||||
'hotel_id' => 2,
|
||||
'name' => '1st Floor',
|
||||
'level' => 1,
|
||||
'status' => 1,
|
||||
'created_at' => Carbon::now(),
|
||||
'updated_at' => Carbon::now(),
|
||||
],
|
||||
[
|
||||
'restaurant_id' => 1,
|
||||
'hotel_id' => 3,
|
||||
'name' => 'Lobby',
|
||||
'level' => 0,
|
||||
'status' => 1,
|
||||
'created_at' => Carbon::now(),
|
||||
'updated_at' => Carbon::now(),
|
||||
],
|
||||
[
|
||||
'restaurant_id' => 1,
|
||||
'hotel_id' => 3,
|
||||
'name' => '1st Floor',
|
||||
'level' => 1,
|
||||
'status' => 1,
|
||||
'created_at' => Carbon::now(),
|
||||
'updated_at' => Carbon::now(),
|
||||
],
|
||||
];
|
||||
|
||||
DB::table('floors')->insert($floors);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Database\Seeders;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class HotelTableSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
$hotels = [
|
||||
[
|
||||
'restaurant_id' => 1,
|
||||
'name' => 'Sunrise Hotel',
|
||||
'location' => 'Dhaka, Bangladesh',
|
||||
'email' => 'info@sunrisehotel.com',
|
||||
'phone' => '+880123456789',
|
||||
'description' => 'A luxurious hotel with stunning sunrise views.',
|
||||
'latitude' => 23.8103,
|
||||
'longitude' => 90.4125,
|
||||
'check_in_time' => '14:00',
|
||||
'check_out_time' => '12:00',
|
||||
'status' => 1,
|
||||
'created_at' => Carbon::now(),
|
||||
'updated_at' => Carbon::now(),
|
||||
],
|
||||
[
|
||||
'restaurant_id' => 1,
|
||||
'name' => 'Moonlight Inn',
|
||||
'location' => 'Chittagong, Bangladesh',
|
||||
'email' => 'contact@moonlightinn.com',
|
||||
'phone' => '+880987654321',
|
||||
'description' => 'Comfortable stays under the moonlight.',
|
||||
'latitude' => 22.3569,
|
||||
'longitude' => 91.7832,
|
||||
'check_in_time' => '14:00',
|
||||
'check_out_time' => '12:00',
|
||||
'status' => 1,
|
||||
'created_at' => Carbon::now(),
|
||||
'updated_at' => Carbon::now(),
|
||||
],
|
||||
[
|
||||
'restaurant_id' => 1,
|
||||
'name' => 'Seaside Resort',
|
||||
'location' => 'Cox\'s Bazar, Bangladesh',
|
||||
'email' => 'info@seasideresort.com',
|
||||
'phone' => '+880192837465',
|
||||
'description' => 'Relax by the beach with scenic views.',
|
||||
'latitude' => 21.4272,
|
||||
'longitude' => 92.0058,
|
||||
'check_in_time' => '14:00',
|
||||
'check_out_time' => '12:00',
|
||||
'status' => 1,
|
||||
'created_at' => Carbon::now(),
|
||||
'updated_at' => Carbon::now(),
|
||||
],
|
||||
];
|
||||
|
||||
DB::table('hotels')->insert($hotels);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Database\Seeders;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class RoomAvailabilityTableSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
$availabilities = [];
|
||||
// Example: generate availability for next 7 days for two rooms
|
||||
$roomIds = [1, 2]; // Room IDs from your rooms table
|
||||
$hotelId = 1;
|
||||
$restaurantId = 1;
|
||||
|
||||
for ($i = 0; $i < 7; $i++) {
|
||||
$date = Carbon::now()->addDays($i)->format('Y-m-d');
|
||||
foreach ($roomIds as $roomId) {
|
||||
$availabilities[] = [
|
||||
'restaurant_id' => $restaurantId,
|
||||
'hotel_id' => $hotelId,
|
||||
'room_id' => $roomId,
|
||||
'total_inventory' => 5,
|
||||
'available_inventory' => rand(1, 5),
|
||||
'date' => $date,
|
||||
'is_available' => true,
|
||||
'is_closed_to_arrival' => false,
|
||||
'is_closed_to_departure' => false,
|
||||
'base_price' => rand(80, 150),
|
||||
'extra_adult_price' => 20,
|
||||
'extra_child_price' => 10,
|
||||
'min_stay' => 1,
|
||||
'max_stay' => 10,
|
||||
'blocked_by_booking_id' => null,
|
||||
'block_type' => 'none',
|
||||
'note' => null,
|
||||
'status' => 1,
|
||||
'created_at' => Carbon::now(),
|
||||
'updated_at' => Carbon::now(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
DB::table('room_availabilities')->insert($availabilities);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Database\Seeders;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class RoomBlockTableSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
$blocks = [
|
||||
[
|
||||
'restaurant_id' => 1,
|
||||
'hotel_id' => 1,
|
||||
'room_id' => 1,
|
||||
'start_date' => Carbon::now()->addDays(2)->format('Y-m-d'),
|
||||
'end_date' => Carbon::now()->addDays(3)->format('Y-m-d'),
|
||||
'start_time' => null,
|
||||
'end_time' => null,
|
||||
'block_type' => 'maintenance',
|
||||
'blocked_inventory' => 2,
|
||||
'priority' => 1,
|
||||
'reason' => 'AC maintenance',
|
||||
'auto_release' => true,
|
||||
'created_by' => 1, // admin user id
|
||||
'status' => 1,
|
||||
'created_at' => Carbon::now(),
|
||||
'updated_at' => Carbon::now(),
|
||||
],
|
||||
[
|
||||
'restaurant_id' => 1,
|
||||
'hotel_id' => 1,
|
||||
'room_id' => 2,
|
||||
'start_date' => Carbon::now()->addDays(1)->format('Y-m-d'),
|
||||
'end_date' => Carbon::now()->addDays(1)->format('Y-m-d'),
|
||||
'start_time' => '12:00:00',
|
||||
'end_time' => '18:00:00',
|
||||
'block_type' => 'deep_clean',
|
||||
'blocked_inventory' => 1,
|
||||
'priority' => 2,
|
||||
'reason' => 'Daily deep cleaning',
|
||||
'auto_release' => true,
|
||||
'created_by' => 1,
|
||||
'status' => 1,
|
||||
'created_at' => Carbon::now(),
|
||||
'updated_at' => Carbon::now(),
|
||||
],
|
||||
];
|
||||
|
||||
DB::table('room_blocks')->insert($blocks);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Database\Seeders;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class RoomTableSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
$roomTypes = [
|
||||
['id' => 1, 'name' => 'Deluxe Room', 'price_min' => 150, 'price_max' => 220],
|
||||
['id' => 2, 'name' => 'Superior Room', 'price_min' => 130, 'price_max' => 190],
|
||||
['id' => 3, 'name' => 'Premier Room', 'price_min' => 200, 'price_max' => 260],
|
||||
['id' => 4, 'name' => 'Executive Room', 'price_min' => 250, 'price_max' => 320],
|
||||
['id' => 5, 'name' => 'Family Room', 'price_min' => 180, 'price_max' => 240],
|
||||
['id' => 6, 'name' => 'Suite', 'price_min' => 350, 'price_max' => 500],
|
||||
];
|
||||
|
||||
$views = ['City View', 'Garden View', 'Pool View', 'Marina View', 'Skyline View'];
|
||||
$rooms = [];
|
||||
$imagePool = range(1, 46); // images 1–46
|
||||
|
||||
for ($i = 1; $i <= 20; $i++) {
|
||||
|
||||
$type = $roomTypes[array_rand($roomTypes)];
|
||||
|
||||
$image = $imagePool[array_rand($imagePool)].'.jpg';
|
||||
$banner = $imagePool[array_rand($imagePool)].'.jpg';
|
||||
|
||||
// Random gallery 6–8 images
|
||||
shuffle($imagePool);
|
||||
$galleryImages = array_slice($imagePool, 0, rand(6, 8));
|
||||
$galleryImages = array_map(fn ($img) => $img.'.jpg', $galleryImages);
|
||||
|
||||
$roomNumber = 100 + $i;
|
||||
$typeSlug = strtolower(str_replace(' ', '-', $type['name']));
|
||||
|
||||
$regular = rand($type['price_min'], $type['price_max']);
|
||||
$offer = $regular - rand(10, 30);
|
||||
|
||||
$rooms[] = [
|
||||
'restaurant_id' => 1,
|
||||
'hotel_id' => 1,
|
||||
'floor_id' => rand(1, 6),
|
||||
'room_type_id' => $type['id'],
|
||||
|
||||
'room_number' => $roomNumber,
|
||||
'room_code' => strtoupper(substr($typeSlug, 0, 3)).$roomNumber,
|
||||
|
||||
'max_adults' => rand(2, 4),
|
||||
'max_children' => rand(0, 2),
|
||||
|
||||
'has_balcony' => rand(0, 1),
|
||||
'has_bathtub' => rand(0, 1),
|
||||
|
||||
'view_type' => $views[array_rand($views)],
|
||||
'slug' => $roomNumber.'-'.$typeSlug,
|
||||
|
||||
'image' => $image,
|
||||
'banner_image' => $banner,
|
||||
'gallery_images' => json_encode($galleryImages),
|
||||
|
||||
'description' => $type['name'].' with modern amenities, designed for comfort and a premium Singapore hotel experience.',
|
||||
|
||||
'regular_price' => $regular,
|
||||
'offer_price' => $offer,
|
||||
|
||||
'is_clean' => true,
|
||||
'is_available' => true,
|
||||
'status' => 1,
|
||||
|
||||
'created_at' => Carbon::now(),
|
||||
'updated_at' => Carbon::now(),
|
||||
];
|
||||
}
|
||||
|
||||
DB::table('rooms')->insert($rooms);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Booking\Database\Seeders;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class RoomTypeTableSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
$roomTypes = [
|
||||
// Hotel 1
|
||||
[
|
||||
'restaurant_id' => 1,
|
||||
'hotel_id' => 1,
|
||||
'name' => 'Deluxe Room',
|
||||
'capacity' => 2,
|
||||
'beds' => 1,
|
||||
'bed_type' => 'Queen',
|
||||
'has_ac' => true,
|
||||
'has_wifi' => true,
|
||||
'has_breakfast' => true,
|
||||
'is_refundable' => true,
|
||||
'base_price' => 120.00,
|
||||
'weekend_price' => 150.00,
|
||||
'extra_guest_price' => 30.00,
|
||||
'description' => 'Spacious deluxe room with modern amenities.',
|
||||
'status' => 1,
|
||||
'created_at' => Carbon::now(),
|
||||
'updated_at' => Carbon::now(),
|
||||
],
|
||||
[
|
||||
'restaurant_id' => 1,
|
||||
'hotel_id' => 1,
|
||||
'name' => 'Standard Room',
|
||||
'capacity' => 2,
|
||||
'beds' => 1,
|
||||
'bed_type' => 'Twin',
|
||||
'has_ac' => true,
|
||||
'has_wifi' => true,
|
||||
'has_breakfast' => false,
|
||||
'is_refundable' => true,
|
||||
'base_price' => 80.00,
|
||||
'weekend_price' => 100.00,
|
||||
'extra_guest_price' => 20.00,
|
||||
'description' => 'Cozy standard room for a comfortable stay.',
|
||||
'status' => 1,
|
||||
'created_at' => Carbon::now(),
|
||||
'updated_at' => Carbon::now(),
|
||||
],
|
||||
// Hotel 2
|
||||
[
|
||||
'restaurant_id' => 1,
|
||||
'hotel_id' => 2,
|
||||
'name' => 'Suite Room',
|
||||
'capacity' => 4,
|
||||
'beds' => 2,
|
||||
'bed_type' => 'King',
|
||||
'has_ac' => true,
|
||||
'has_wifi' => true,
|
||||
'has_breakfast' => true,
|
||||
'is_refundable' => true,
|
||||
'base_price' => 200.00,
|
||||
'weekend_price' => 250.00,
|
||||
'extra_guest_price' => 50.00,
|
||||
'description' => 'Luxury suite with separate living area.',
|
||||
'status' => 1,
|
||||
'created_at' => Carbon::now(),
|
||||
'updated_at' => Carbon::now(),
|
||||
],
|
||||
[
|
||||
'restaurant_id' => 1,
|
||||
'hotel_id' => 2,
|
||||
'name' => 'Standard Room',
|
||||
'capacity' => 2,
|
||||
'beds' => 1,
|
||||
'bed_type' => 'Queen',
|
||||
'has_ac' => true,
|
||||
'has_wifi' => true,
|
||||
'has_breakfast' => false,
|
||||
'is_refundable' => true,
|
||||
'base_price' => 90.00,
|
||||
'weekend_price' => 120.00,
|
||||
'extra_guest_price' => 25.00,
|
||||
'description' => 'Comfortable room suitable for short stays.',
|
||||
'status' => 1,
|
||||
'created_at' => Carbon::now(),
|
||||
'updated_at' => Carbon::now(),
|
||||
],
|
||||
// Hotel 3
|
||||
[
|
||||
'restaurant_id' => 1,
|
||||
'hotel_id' => 3,
|
||||
'name' => 'Seaside Deluxe',
|
||||
'capacity' => 3,
|
||||
'beds' => 2,
|
||||
'bed_type' => 'Queen',
|
||||
'has_ac' => true,
|
||||
'has_wifi' => true,
|
||||
'has_breakfast' => true,
|
||||
'is_refundable' => true,
|
||||
'base_price' => 150.00,
|
||||
'weekend_price' => 180.00,
|
||||
'extra_guest_price' => 35.00,
|
||||
'description' => 'Deluxe room with sea view.',
|
||||
'status' => 1,
|
||||
'created_at' => Carbon::now(),
|
||||
'updated_at' => Carbon::now(),
|
||||
],
|
||||
[
|
||||
'restaurant_id' => 1,
|
||||
'hotel_id' => 3,
|
||||
'name' => 'Seaside Standard',
|
||||
'capacity' => 2,
|
||||
'beds' => 1,
|
||||
'bed_type' => 'Twin',
|
||||
'has_ac' => true,
|
||||
'has_wifi' => true,
|
||||
'has_breakfast' => false,
|
||||
'is_refundable' => true,
|
||||
'base_price' => 100.00,
|
||||
'weekend_price' => 130.00,
|
||||
'extra_guest_price' => 25.00,
|
||||
'description' => 'Affordable standard room with a partial sea view.',
|
||||
'status' => 1,
|
||||
'created_at' => Carbon::now(),
|
||||
'updated_at' => Carbon::now(),
|
||||
],
|
||||
];
|
||||
|
||||
DB::table('room_types')->insert($roomTypes);
|
||||
}
|
||||
}
|
||||
11
public/restaurant/Modules/Booking/module.json
Normal file
11
public/restaurant/Modules/Booking/module.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "Booking",
|
||||
"alias": "booking",
|
||||
"description": "",
|
||||
"keywords": [],
|
||||
"priority": 0,
|
||||
"providers": [
|
||||
"Modules\\Booking\\Providers\\BookingServiceProvider"
|
||||
],
|
||||
"files": []
|
||||
}
|
||||
15
public/restaurant/Modules/Booking/package.json
Normal file
15
public/restaurant/Modules/Booking/package.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"axios": "^1.1.2",
|
||||
"laravel-vite-plugin": "^0.7.5",
|
||||
"sass": "^1.69.5",
|
||||
"postcss": "^8.3.7",
|
||||
"vite": "^4.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
|
||||
<title>Booking Module - {{ config('app.name', 'Laravel') }}</title>
|
||||
|
||||
<meta name="description" content="{{ $description ?? '' }}">
|
||||
<meta name="keywords" content="{{ $keywords ?? '' }}">
|
||||
<meta name="author" content="{{ $author ?? '' }}">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.bunny.net">
|
||||
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
|
||||
|
||||
{{-- Vite CSS --}}
|
||||
{{-- {{ module_vite('build-booking', 'resources/assets/sass/app.scss') }} --}}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{{ $slot }}
|
||||
|
||||
{{-- Vite JS --}}
|
||||
{{-- {{ module_vite('build-booking', 'resources/assets/js/app.js') }} --}}
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,5 @@
|
||||
<x-booking::layouts.master>
|
||||
<h1>Hello World</h1>
|
||||
|
||||
<p>Module: {!! config('booking.name') !!}</p>
|
||||
</x-booking::layouts.master>
|
||||
0
public/restaurant/Modules/Booking/routes/.gitkeep
Normal file
0
public/restaurant/Modules/Booking/routes/.gitkeep
Normal file
25
public/restaurant/Modules/Booking/routes/api.php
Normal file
25
public/restaurant/Modules/Booking/routes/api.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Modules\Booking\Http\Controllers\API\BookingController;
|
||||
use Modules\Booking\Http\Controllers\API\FloorController;
|
||||
use Modules\Booking\Http\Controllers\API\HotelController;
|
||||
use Modules\Booking\Http\Controllers\API\RoomAvailabilityController;
|
||||
use Modules\Booking\Http\Controllers\API\RoomBlockController;
|
||||
use Modules\Booking\Http\Controllers\API\RoomController;
|
||||
use Modules\Booking\Http\Controllers\API\RoomTypeController;
|
||||
|
||||
Route::prefix('/v1')->group(
|
||||
function () {
|
||||
// Protected Routes (Requires Authentication)
|
||||
Route::middleware(['auth:api'])->group(function () {
|
||||
Route::apiResource('hotels', HotelController::class);
|
||||
Route::apiResource('floors', FloorController::class);
|
||||
Route::apiResource('room-types', RoomTypeController::class);
|
||||
Route::apiResource('rooms', RoomController::class);
|
||||
Route::apiResource('room-availabilities', RoomAvailabilityController::class);
|
||||
Route::apiResource('room-blocks', RoomBlockController::class);
|
||||
Route::apiResource('bookings', BookingController::class);
|
||||
});
|
||||
}
|
||||
);
|
||||
57
public/restaurant/Modules/Booking/vite.config.js
Normal file
57
public/restaurant/Modules/Booking/vite.config.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import laravel from 'laravel-vite-plugin';
|
||||
import { readdirSync, statSync } from 'fs';
|
||||
import { join,relative,dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
export default defineConfig({
|
||||
build: {
|
||||
outDir: '../../public/build-booking',
|
||||
emptyOutDir: true,
|
||||
manifest: true,
|
||||
},
|
||||
plugins: [
|
||||
laravel({
|
||||
publicDirectory: '../../public',
|
||||
buildDirectory: 'build-booking',
|
||||
input: [
|
||||
__dirname + '/resources/assets/sass/app.scss',
|
||||
__dirname + '/resources/assets/js/app.js'
|
||||
],
|
||||
refresh: true,
|
||||
}),
|
||||
],
|
||||
});
|
||||
// Scen all resources for assets file. Return array
|
||||
//function getFilePaths(dir) {
|
||||
// const filePaths = [];
|
||||
//
|
||||
// function walkDirectory(currentPath) {
|
||||
// const files = readdirSync(currentPath);
|
||||
// for (const file of files) {
|
||||
// const filePath = join(currentPath, file);
|
||||
// const stats = statSync(filePath);
|
||||
// if (stats.isFile() && !file.startsWith('.')) {
|
||||
// const relativePath = 'Modules/Booking/'+relative(__dirname, filePath);
|
||||
// filePaths.push(relativePath);
|
||||
// } else if (stats.isDirectory()) {
|
||||
// walkDirectory(filePath);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// walkDirectory(dir);
|
||||
// return filePaths;
|
||||
//}
|
||||
|
||||
//const __filename = fileURLToPath(import.meta.url);
|
||||
//const __dirname = dirname(__filename);
|
||||
|
||||
//const assetsDir = join(__dirname, 'resources/assets');
|
||||
//export const paths = getFilePaths(assetsDir);
|
||||
|
||||
|
||||
//export const paths = [
|
||||
// 'Modules/Booking/resources/assets/sass/app.scss',
|
||||
// 'Modules/Booking/resources/assets/js/app.js',
|
||||
//];
|
||||
Reference in New Issue
Block a user