parkModel = $parkModel; $this->contractModel = $contractModel; $this->shjEightService = $shjEightService; } /** * SHJ-4C 室割当処理メイン実行 * * 処理フロー: * 【処理1】ゾーン情報取得 * 【判断1】割当判定 * 【処理2】バッチログ作成 * 【処理3】処理結果返却 * * @param int $parkId 駐輪場ID * @param int $ptypeId 駐輪分類ID * @param int $psectionId 車種区分ID * @return array 処理結果 */ public function executeRoomAllocation(int $parkId, int $ptypeId, int $psectionId): array { $statusComment = ''; $status = 'success'; try { Log::info('SHJ-4C 室割当処理開始', [ 'park_id' => $parkId, 'ptype_id' => $ptypeId, 'psection_id' => $psectionId ]); // 【処理1】ゾーン情報取得 $zoneInfo = $this->getZoneInformation($parkId, $ptypeId, $psectionId); if (empty($zoneInfo)) { $message = '対象のゾーン情報が見つかりません'; $status = 'error'; $statusComment = sprintf('エラー: %s (park_id:%d, ptype_id:%d, psection_id:%d)', $message, $parkId, $ptypeId, $psectionId); // バッチログ作成 $this->createBatchLog($status, $statusComment); // JOB3: ゾーンID, 車室番号, 異常情報を返却 return [ 'success' => false, 'zone_id' => null, 'pplace_no' => null, 'error_info' => $message ]; } // 【判断1】割当判定処理 $allocationResult = $this->performAllocationJudgment($zoneInfo, $parkId, $ptypeId, $psectionId); if (!$allocationResult['can_allocate']) { // 割当NGの場合、対象事室番号を設定 $this->setTargetRoomNumber($allocationResult['target_room_number']); $status = 'warning'; $statusComment = sprintf('割当NG: %s (park_id:%d, ptype_id:%d, psection_id:%d)', $allocationResult['reason'], $parkId, $ptypeId, $psectionId); // バッチログ作成 $this->createBatchLog($status, $statusComment); // JOB3: ゾーンID, 車室番号, 異常情報を返却(割当NG = 空き車室なし) return [ 'success' => true, 'zone_id' => null, 'pplace_no' => null, 'error_info' => $allocationResult['reason'] ]; } // 【処理2】バッチログ作成 $statusComment = sprintf('室割当処理完了 (park_id:%d, ptype_id:%d, psection_id:%d, zone_id:%d, pplace_no:%d)', $parkId, $ptypeId, $psectionId, $allocationResult['zone_id'], $allocationResult['pplace_no']); $this->createBatchLog($status, $statusComment); Log::info('SHJ-4C 室割当処理完了', [ 'zone_id' => $allocationResult['zone_id'], 'pplace_no' => $allocationResult['pplace_no'] ]); // 【処理3】処理結果返却 // JOB3: ゾーンID, 車室番号, 異常情報を返却 return [ 'success' => true, 'message' => 'SHJ-4C 室割当処理が正常に完了しました', 'zone_id' => $allocationResult['zone_id'], 'pplace_no' => $allocationResult['pplace_no'], 'error_info' => null ]; } catch (\Exception $e) { $errorMessage = 'SHJ-4C 室割当処理でエラーが発生: ' . $e->getMessage(); $status = 'error'; $statusComment = sprintf('例外エラー: %s (park_id:%d, ptype_id:%d, psection_id:%d)', $e->getMessage(), $parkId, $ptypeId, $psectionId); // バッチログ作成 $this->createBatchLog($status, $statusComment); Log::error('SHJ-4C 室割当処理エラー', [ 'exception' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); // JOB3: ゾーンID, 車室番号, 異常情報を返却 return [ 'success' => false, 'zone_id' => null, 'pplace_no' => null, 'error_info' => $errorMessage ]; } } /** * SHJ-8バッチ処理ログ作成 * * @param string $status ステータス * @param string $statusComment ステータスコメント * @return void */ private function createBatchLog(string $status, string $statusComment): void { try { $device = Device::orderBy('device_id')->first(); $deviceId = $device ? $device->device_id : 1; $today = now()->format('Y/m/d'); Log::info('SHJ-8バッチ処理ログ作成', [ 'device_id' => $deviceId, 'process_name' => 'SHJ-4C', 'job_name' => 'SHJ-4C室割当', 'status' => $status, 'status_comment' => $statusComment ]); // SHJ-8サービスを呼び出し $this->shjEightService->execute( $deviceId, 'SHJ-4C', 'SHJ-4C室割当', $status, $statusComment, $today, $today ); } catch (\Exception $e) { Log::error('SHJ-8 バッチログ作成エラー', [ 'error' => $e->getMessage() ]); } } /** * 【処理1】ゾーン情報取得 * * 駐輪場ID、駐輪分類ID、車種区分IDに紐づくゾーン情報を取得する * SQLクエリは設計書の仕様に基づく * * @param int $parkId 駐輪場ID * @param int $ptypeId 駐輪分類ID * @param int $psectionId 車種区分ID * @return array ゾーン情報 */ private function getZoneInformation(int $parkId, int $ptypeId, int $psectionId): array { try { // 設計書に記載されたSQLクエリに基づくゾーン情報取得 $zoneInfo = DB::table('zone as T1') ->select([ 'T1.zone_id', 'T1.zone_name', 'T1.zone_number as zone_number', 'T1.zone_standard as zone_standard', 'T1.zone_tolerance as zone_tolerance', 'T1.zone_sort', 'T2.update_grace_period_start_date', 'T2.update_grace_period_end_date' ]) ->join('park as T2', 'T1.park_id', '=', 'T2.park_id') ->where('T1.park_id', $parkId) ->where('T1.ptype_id', $ptypeId) ->where('T1.psection_id', $psectionId) ->where('T1.delete_flag', 0) ->orderBy('T1.zone_sort') ->get() ->toArray(); Log::info('ゾーン情報取得完了', [ 'park_id' => $parkId, 'ptype_id' => $ptypeId, 'psection_id' => $psectionId, 'zone_count' => count($zoneInfo) ]); return $zoneInfo; } catch (\Exception $e) { Log::error('ゾーン情報取得エラー', [ 'park_id' => $parkId, 'ptype_id' => $ptypeId, 'psection_id' => $psectionId, 'error' => $e->getMessage() ]); throw $e; } } /** * 【判断1】割当判定処理 * * ゾーンIDごとにわか順(インクリメント、内部変数.対象番号とする)に * ゾーン内標準台数に達するまで、定期契約マスタから該当する車室番号で * 契約済みの情報の有無を確認する * * @param array $zoneInfo ゾーン情報 * @param int $parkId 駐輪場ID * @param int $ptypeId 駐輪分類ID * @param int $psectionId 車種区分ID * @return array 割当判定結果 */ private function performAllocationJudgment(array $zoneInfo, int $parkId, int $ptypeId, int $psectionId): array { try { foreach ($zoneInfo as $zone) { // ゾーン内標準台数まで車室番号をインクリメントして検索 for ($pplaceNo = 1; $pplaceNo <= $zone->zone_standard; $pplaceNo++) { // 該当する車室番号で契約済みの情報の有無を確認 $contractInfo = $this->getRegularContractInfo( $parkId, $zone->zone_id, $pplaceNo, (int) $zone->update_grace_period_start_date, (int) $zone->update_grace_period_end_date ); // 契約情報が存在しない場合、この車室番号は空き if ($contractInfo === null) { Log::info('SHJ-4C 割当OK', [ 'zone_id' => $zone->zone_id, 'zone_name' => $zone->zone_name, 'pplace_no' => $pplaceNo, ]); return [ 'can_allocate' => true, 'zone_id' => $zone->zone_id, 'zone_name' => $zone->zone_name, 'pplace_no' => $pplaceNo, 'reason' => "割当OK: ゾーンID {$zone->zone_id}, 車室番号 {$pplaceNo}" ]; } } Log::info('SHJ-4C ゾーン満杯', [ 'zone_id' => $zone->zone_id, 'zone_name' => $zone->zone_name, ]); } // 全ゾーンで割当NGの場合 $targetRoomNumber = $this->generateTargetRoomNumber($parkId, $ptypeId, $psectionId); Log::warning('SHJ-4C 割当NG', [ 'target_room_number' => $targetRoomNumber, ]); return [ 'can_allocate' => false, 'target_room_number' => $targetRoomNumber, 'reason' => "車室割り当てNG: " . $targetRoomNumber ]; } catch (\Exception $e) { Log::error('割当判定処理エラー', [ 'error' => $e->getMessage() ]); throw $e; } } /** * 定期契約情報取得 * * 設計書のSQLクエリに基づく定期契約マスタ検索 * 特定の車室番号で契約済みの情報の有無を確認する * * @param int $parkId 駐輪場ID * @param int $zoneId ゾーンID * @param int $pplaceNo 車室番号(対象番号) * @param int $updateGracePeriodStartDate 更新期間開始日 * @param int $updateGracePeriodEndDate 更新期間終了日 * @return array|null 定期契約情報(存在しない場合はnull) */ private function getRegularContractInfo( int $parkId, int $zoneId, int $pplaceNo, int $updateGracePeriodStartDate, int $updateGracePeriodEndDate ): ?array { $currentDate = Carbon::now()->format('y.%m.%d'); $query = DB::table('regular_contract as T1') ->select(['T1.contract_id']) ->where('T1.park_id', $parkId) ->where('T1.zone_id', $zoneId) ->where('T1.pplace_no', $pplaceNo); // 【「JOB1.更新期間開始日」<=「JOB1. 更新期間終了日」の場合】 if ($updateGracePeriodStartDate <= $updateGracePeriodEndDate) { // パターンA: T1.有効期間E >= date_format(now(), '%y.%m.%d') $query->whereRaw("date_format(T1.contract_periode, '%y.%m.%d') >= ?", [$currentDate]); } else { // 【その他の場合】 // パターンB: T1.有効期間E + 「JOB1. 更新期間終了日」>= date_format(now(), '%y.%m.%d') $query->whereRaw("DATE_ADD(T1.contract_periode, INTERVAL ? DAY) >= ?", [$updateGracePeriodEndDate, $currentDate]); } $query->where('T1.contract_flag', 1); $result = $query->first(); return $result ? (array) $result : null; } /** * 対象事室番号生成 * * @param int $parkId 駐輪場ID * @param int $ptypeId 駐輪分類ID * @param int $psectionId 車種区分ID * @return string 対象事室番号 */ private function generateTargetRoomNumber(int $parkId, int $ptypeId, int $psectionId): string { return sprintf('%d_%d_%d', $parkId, $ptypeId, $psectionId); } /** * 対象事室番号設定 * * @param string $targetRoomNumber 対象事室番号 * @return void */ private function setTargetRoomNumber(string $targetRoomNumber): void { Log::info('対象事室番号設定', [ 'target_room_number' => $targetRoomNumber ]); // 実際の事室番号設定ロジックをここに実装 // 具体的な仕様が必要な場合は後で追加実装 } }