diff --git a/app/Console/Commands/CreateParkingUtilizationRate.php b/app/Console/Commands/CreateParkingUtilizationRate.php index a000fbe..21e3741 100644 --- a/app/Console/Commands/CreateParkingUtilizationRate.php +++ b/app/Console/Commands/CreateParkingUtilizationRate.php @@ -4,7 +4,9 @@ namespace App\Console\Commands; use App\Models\ParkingAccessRecord; use App\Models\ParkingSpace; +use App\Models\ParkingSpaceType; use App\Models\ParkingUtilizationRate; +use App\Services\ParkingOccupancyRateService; use Exception; use Illuminate\Console\Command; use Illuminate\Support\Facades\DB; @@ -58,6 +60,24 @@ class CreateParkingUtilizationRate extends Command } } } + + $ParkingSpaceTypeIds = ParkingSpaceType::query()->pluck('id'); + foreach ($ParkingSpaceTypeIds as $space_type_id) { + $this->createOccupancyRate( + $start_time, + $end_time, + $space_type_id + ); + + if ($now_hours == '01') { + $this->createOccupancyRate( + $day_start_time, + $day_end_time, + $space_type_id, + 2 + ); + } + } DB::commit(); } catch (Exception $e) { DB::rollBack(); @@ -141,4 +161,48 @@ class CreateParkingUtilizationRate extends Command } return $rate * 100; } + + private function createOccupancyRate( + $start_time, + $end_time, + $space_type_id, + $time_type = 1 + ) { + $start_times = strtotime($start_time); + $end_times = strtotime($end_time); + $list = ParkingSpace::query()->where( + 'space_type_id', + $space_type_id + )->select(['floor_id', 'id'])->get()->toArray(); + + $floor_data = []; + foreach ($list as $item) { + $floor_data[$item['floor_id']][] = $item['id']; + } + foreach ($floor_data as $floor_id => $space_ids) { + $sum_count = count($space_ids); + $occupy_space_ids = ParkingAccessRecord::query()->whereIn( + 'space_id', + $space_ids + )->where( + function ($query) use ($start_times, $end_times) { + $query->whereRaw( + " enter_time < {$end_times}" + . + " AND (leave_time IS NULL OR leave_time > {$start_times})" + ); + } + )->pluck('space_id')->toArray(); + $unique_space_ids = array_unique($occupy_space_ids); + $occupy_count = count($unique_space_ids); + ParkingOccupancyRateService::createData( + $space_type_id, + $floor_id, + $start_time, + $sum_count, + $occupy_count, + $time_type + ); + } + } } diff --git a/app/Exports/OccupancyRateExport.php b/app/Exports/OccupancyRateExport.php new file mode 100644 index 0000000..bc204c3 --- /dev/null +++ b/app/Exports/OccupancyRateExport.php @@ -0,0 +1,66 @@ +parents = $parents; + $this->service = new ParkingOccupancyRateService( + new OperationLogService() + ); + $this->parkingSpaceType = ParkingSpaceType::getData(); + } + + public function array(): array + { + $data = []; + $index = 1; + $parkingSpaceType = $this->parkingSpaceType; + $query = $this->service->queryList($this->parents); + $list = $query->select()->get()->toArray(); + foreach ($list as $item) { + $item = $this->service->getItem($item, $parkingSpaceType); + $value = [ + 'index' => $index, + 'time' => $item['time'], + 'floor' => $item['floor'] + ]; + foreach ($parkingSpaceType as $type) { + $type_id = $type['id']; + $key = 'type' . $type_id; + $value[$key] = $item[$key]; + } + $data[] = $value; + $index += 1; + } + return $data; + } + + /** + * @return array + */ + public function headings(): array + { + $header = [ + __('exports.global.index'), + __('exports.occupancy_rate.time'), + __('exports.occupancy_rate.floor'), + ]; + foreach ($this->parkingSpaceType as $value) { + $header[] = $value['name']; + } + return $header; + } +} diff --git a/app/Http/Controllers/Admin/OccupancyRateController.php b/app/Http/Controllers/Admin/OccupancyRateController.php new file mode 100644 index 0000000..7ed2221 --- /dev/null +++ b/app/Http/Controllers/Admin/OccupancyRateController.php @@ -0,0 +1,149 @@ +service = $service; + } + + public function index(Request $request): JsonResponse + { + try { + $query = $this->service->queryList($request->all()); + // 分页 + $page = $request->input('page', 1); + $perPage = $request->input('per_page', 10); + $total = $query->count(); + $items = $query->latest()->forPage($page, $perPage)->select()->get( + ); + if ($items) { + $items = $items->toArray(); + $parkingSpaceType = ParkingSpaceType::getData(); + foreach ($items as &$item) { + $item = $this->service->getItem($item, $parkingSpaceType); + } + } + return $this->responseService->success([ + 'items' => $items, + 'total' => $total, + 'page' => $page, + 'per_page' => $perPage, + 'last_page' => ceil($total / $perPage), + ]); + } catch (Exception $e) { + $m_prefix = __('exception.exception_handler.resource'); + return $this->responseService->systemError( + $m_prefix . ':' . $e->getMessage() + ); + } + } + + public function search(): JsonResponse + { + try { + $data = [ + 'space_type_list' => ParkingSpaceType::getData(), + 'parking_list' => Parking::getData() + ]; + return $this->responseService->success($data); + } catch (Exception $e) { + $m_prefix = __('exception.exception_handler.resource'); + return $this->responseService->systemError( + $m_prefix . ':' . $e->getMessage() + ); + } + } + + public function chart(Request $request): JsonResponse + { + try { + $query = $this->service->queryList($request->all()); + $list = $query->groupBy(['time', 'time_type'])->select( + ['time', 'time_type'] + )->get(); + $ParkingSpaceType = array_column( + ParkingSpaceType::getData(), + 'name', + 'id' + ); + foreach ($list as &$item) { + $time = $item['time']; + $time_type = $item['time_type']; + if ($time_type == 1) { + $format = 'm-d H:i'; + } else { + $format = 'Y-m-d'; + } + $item['time'] = date($format, strtotime($time)); + $ids = ParkingOccupancyRate::query()->where('time', $time) + ->where('time_type', $time_type)->pluck('id'); + $space_type_list = ParkingOccupancyRateInfo::query()->whereIn( + 'occupancy_rate_id', + $ids + )->groupBy(['space_type_id'])->selectRaw( + 'SUM(`occupy_count`) as sum_occupy_count, space_type_id' + )->get()->toArray(); + foreach ($space_type_list as $key => $value) { + $space_type_list[$key]['space_type_name'] + = $ParkingSpaceType[$value['space_type_id']]; + unset($space_type_list[$key]['space_type_id']); + } + $item['space_type_list'] = $space_type_list; + unset($item['time_type'], $item['floor_id']); + } + return $this->responseService->success($list); + } catch (Exception $e) { + $m_prefix = __('exception.exception_handler.resource'); + return $this->responseService->systemError( + $m_prefix . ':' . $e->getMessage() + ); + } + } + + /** + * @param Request $request + * @return BinaryFileResponse + */ + public function export(Request $request): BinaryFileResponse + { + return Excel::download( + new OccupancyRateExport($request->all()), + set_space_underline(__('exports.access_record.list')) + . get_datetime('date_') + . '.xlsx' + ); + } +} diff --git a/app/Models/AdminFloor.php b/app/Models/AdminFloor.php index 0af71a5..267da64 100644 --- a/app/Models/AdminFloor.php +++ b/app/Models/AdminFloor.php @@ -72,4 +72,9 @@ class AdminFloor extends Model { return self::query()->where('id', $id)->value('building_floor') ?? ''; } + + public static function getIdsParking($parking_id) + { + return self::query()->where('building_floor', $parking_id)->pluck('id'); + } } diff --git a/app/Models/ParkingOccupancyRate.php b/app/Models/ParkingOccupancyRate.php new file mode 100644 index 0000000..6d1bffc --- /dev/null +++ b/app/Models/ParkingOccupancyRate.php @@ -0,0 +1,25 @@ +where([ + 'occupancy_rate_id' => $occupancy_rate_id, + 'space_type_id' => $space_type_id + ])->first(['sum_count', 'occupy_count']); + } +} diff --git a/app/Services/ParkingOccupancyRateService.php b/app/Services/ParkingOccupancyRateService.php new file mode 100644 index 0000000..e6d4386 --- /dev/null +++ b/app/Services/ParkingOccupancyRateService.php @@ -0,0 +1,125 @@ + $floor_id, + 'time' => $time, + 'time_type' => $time_type + ]; + $id = ParkingOccupancyRate::query()->where($where)->value('id'); + if (!$id) { + $data = [ + 'floor_id' => $floor_id, + 'time' => $time, + 'time_type' => $time_type, + 'created_at' => date("Y-m-d H:i:s", time()), + 'updated_at' => date("Y-m-d H:i:s", time()) + ]; + $model = ParkingOccupancyRate::query()->create($data); + $id = $model->toArray()['id']; + } + $dataInfo = $whereInfo = [ + 'occupancy_rate_id' => $id, + 'space_type_id' => $space_type_id + ]; + if (ParkingOccupancyRateInfo::query()->where($whereInfo)->exists()) { + return; + } + $dataInfo['sum_count'] = $sum_count; + $dataInfo['occupy_count'] = $occupy_count; + ParkingOccupancyRateInfo::query()->create($dataInfo); + } + + public function queryList($param) + { + $query = ParkingOccupancyRate::query(); + + $floor_id = ''; + if (isset($param['floor_id']) && !empty($param['floor_id'])) { + $floor_id = $param['floor_id']; + $query->where('floor_id', $param['floor_id']); + } + + if (isset($param['parking_id']) && !empty($param['parking_id']) + && !$floor_id + ) { + $floor_ids = AdminFloor::getIdsParking($param['parking_id']); + if ($floor_ids) { + $query->whereIn('floor_id', $floor_ids); + } + } + + $start_time = $param['start_date'] ?? date("Y-m-d 00:00:00"); + $end_time = $param['end_date'] ?? date("Y-m-d 23:59:59"); + + $start_date = date('Y-m-d', strtotime($start_time)); + $end_date = date('Y-m-d', strtotime($end_time)); + + $time_type = 2; + if ($start_date == $end_date) { + $time_type = 1; + } + $query->where('time_type', $time_type); + + if ($start_time && strtotime($start_time)) { + $start_time = date("Y-m-d H:i:s", strtotime($start_time)); + if ($start_time) { + $query->where('time', '>=', $start_time); + } + } + + if ($end_time && strtotime($end_time)) { + $end_time = date("Y-m-d H:i:s", strtotime($end_time)); + if ($end_time) { + $query->where('time', '<=', $end_time); + } + } + + $query->orderBy('time', 'desc'); + return $query; + } + + public function getItem($item, $parkingSpaceType) + { + $item['floor'] = AdminFloor::getName($item['floor_id']); + if ($item['time_type'] == 1) { + $format = 'Y-m-d H:i'; + } else { + $format = 'Y-m-d'; + } + $item['time'] = date($format, strtotime($item['time'])); + foreach ($parkingSpaceType as $spaceType) { + $type_id = $spaceType['id']; + $key = 'type' . $type_id; + $item[$key]['sum_count'] = 0; + $item[$key]['occupy_count'] = 0; + $res = ParkingOccupancyRateInfo::getTypeCount( + $item['id'], + $type_id + ); + if ($res) { + $item[$key]['sum_count'] = $res['sum_count']; + $item[$key]['occupy_count'] = $res['occupy_count']; + } + } + unset($item['floor_id'], $item['time_type']); + return $item; + } +} diff --git a/database/migrations/2026_09_09_143449_create_parking_occupancy_rate_info_table.php b/database/migrations/2026_09_09_143449_create_parking_occupancy_rate_info_table.php new file mode 100644 index 0000000..f099ce5 --- /dev/null +++ b/database/migrations/2026_09_09_143449_create_parking_occupancy_rate_info_table.php @@ -0,0 +1,32 @@ +id(); + $table->integer('occupancy_rate_id')->comment('主表id'); + $table->integer('space_type_id')->comment('车位类型id'); + $table->integer('sum_count')->default(0)->comment('车位总数量'); + $table->integer('occupy_count')->default(0)->comment('车位占用数量'); + $table->innoDb(); + $table->comment('停车位占用率详情'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('parking_occupancy_rate'); + } +}; diff --git a/database/migrations/2026_09_09_143449_create_parking_occupancy_rate_table.php b/database/migrations/2026_09_09_143449_create_parking_occupancy_rate_table.php new file mode 100644 index 0000000..a1bcd6f --- /dev/null +++ b/database/migrations/2026_09_09_143449_create_parking_occupancy_rate_table.php @@ -0,0 +1,31 @@ +id(); + $table->integer('floor_id')->comment('楼层id'); + $table->tinyInteger('time_type')->default(1)->comment('1小时 2天'); + $table->dateTime('time')->comment('时间'); + $table->innoDb(); + $table->comment('停车位占用率'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('parking_occupancy_rate'); + } +}; diff --git a/routes/admin/api.php b/routes/admin/api.php index 8682cef..8f4263a 100644 --- a/routes/admin/api.php +++ b/routes/admin/api.php @@ -45,6 +45,7 @@ use App\Http\Controllers\Admin\ProhibitedPassageController; use App\Http\Controllers\Admin\ParkingAccessRecordController; use App\Http\Controllers\Admin\UtilizationRateController; use App\Http\Controllers\Admin\ParkingManualCorrectionController; +use App\Http\Controllers\Admin\OccupancyRateController; Route::group(['prefix' => 'admin'], function () { // 认证相关接口 @@ -264,6 +265,12 @@ Route::group(['prefix' => 'admin'], function () { Route::get('/utilizationRate/search', [UtilizationRateController::class, 'search']); Route::get('/utilizationRate/rule', [UtilizationRateController::class, 'rule']); Route::get('/utilizationRate/export', [UtilizationRateController::class, 'export']); + // 停车位占用率 + Route::get('/occupancyRate', [OccupancyRateController::class, 'index']); + Route::get('/occupancyRate/index', [OccupancyRateController::class, 'chart']); + Route::get('/occupancyRate/search', [OccupancyRateController::class, 'search']); + Route::get('/occupancyRate/rule', [OccupancyRateController::class, 'rule']); + Route::get('/occupancyRate/export', [OccupancyRateController::class, 'export']); // 车牌修正记录 Route::get('/manualCorrection', [ParkingManualCorrectionController::class, 'index']); Route::get('/manualCorrection/rule', [ParkingManualCorrectionController::class, 'rule']);