parkModel = $parkModel; $this->contractModel = $contractModel; $this->earningsSummaryModel = $earningsSummaryModel; $this->psectionModel = $psectionModel; $this->batchLogModel = $batchLogModel; $this->operatorQueModel = $operatorQueModel; } /** * SHJ-9 売上集計処理メイン実行 * * 処理フロー: * 【処理1】集計対象を設定する * 【処理2】駐輪場マスタを取得する * 【判断1】取得件数判定 * 【処理3】車種区分毎に算出する * 【判断2】取得判定 * 【処理4】売上集計結果を削除→登録する * 【処理5】オペレータキュー作成およびバッチ処理ログを作成する * * @param string $type 集計種別(daily/monthly/yearly) * @param string $aggregationDate 集計対象日 * @return array 処理結果 */ public function executeEarningsAggregation(string $type, string $aggregationDate): array { $batchLogId = null; try { // 【処理1】集計対象を設定する $aggregationTarget = $this->setAggregationTarget($type, $aggregationDate); // バッチ処理開始ログ作成 $batchLog = BatchLog::createBatchLog( 'shj9', BatchLog::STATUS_START, [ 'type' => $type, 'aggregation_date' => $aggregationDate, 'aggregation_target' => $aggregationTarget ], "SHJ-9 売上集計処理開始 ({$type})" ); $batchLogId = $batchLog->id; Log::info('SHJ-9 売上集計処理開始', [ 'batch_log_id' => $batchLogId, 'type' => $type, 'aggregation_date' => $aggregationDate, 'aggregation_target' => $aggregationTarget ]); // 【処理2】駐輪場マスタを取得する $parkInfo = $this->getParkInformation(); // 【判断1】取得件数判定 if (empty($parkInfo)) { $message = '売上集計(' . $this->getTypeLabel($type) . '):駐輪場マスタが存在していません。'; // バッチログ更新 $batchLog->update([ 'status' => BatchLog::STATUS_WARNING, 'end_time' => now(), 'message' => $message, 'success_count' => 1 // 処理は成功したが対象なし ]); // 【処理5】オペレータキュー作成 $this->createOperatorQueue($message, $batchLogId); return [ 'success' => true, 'message' => $message, 'processed_parks' => 0, 'summary_records' => 0, 'batch_log_id' => $batchLogId ]; } // 【処理3】車種区分毎に算出する & 【処理4】売上集計結果を削除→登録する $summaryRecords = 0; $processedParks = 0; foreach ($parkInfo as $park) { $parkSummaryRecords = $this->processEarningsForPark($park, $aggregationTarget, $type); if ($parkSummaryRecords > 0) { $processedParks++; $summaryRecords += $parkSummaryRecords; } } // 【判断2】取得判定 if ($summaryRecords === 0) { $message = '対象なしの結果を設定する(契約)'; // バッチログ更新 $batchLog->update([ 'status' => BatchLog::STATUS_WARNING, 'end_time' => now(), 'message' => $message, 'success_count' => 1 ]); // 【処理5】オペレータキュー作成 $this->createOperatorQueue($message, $batchLogId); return [ 'success' => true, 'message' => $message, 'processed_parks' => $processedParks, 'summary_records' => 0, 'batch_log_id' => $batchLogId ]; } // バッチ処理完了ログ更新 $completionMessage = "SHJ-9 売上集計処理正常完了 ({$type}) - 駐輪場数: {$processedParks}, 集計レコード数: {$summaryRecords}"; $batchLog->update([ 'status' => BatchLog::STATUS_SUCCESS, 'end_time' => now(), 'message' => $completionMessage, 'success_count' => 1 ]); // 【処理5】オペレータキュー作成 $this->createOperatorQueue($completionMessage, $batchLogId); Log::info('SHJ-9 売上集計処理完了', [ 'batch_log_id' => $batchLogId, 'processed_parks' => $processedParks, 'summary_records' => $summaryRecords ]); return [ 'success' => true, 'message' => 'SHJ-9 売上集計処理が正常に完了しました', 'processed_parks' => $processedParks, 'summary_records' => $summaryRecords, 'batch_log_id' => $batchLogId ]; } catch (\Exception $e) { $errorMessage = 'SHJ-9 売上集計処理でエラーが発生: ' . $e->getMessage(); if (isset($batchLog) && $batchLog) { $batchLog->update([ 'status' => BatchLog::STATUS_ERROR, 'end_time' => now(), 'message' => $errorMessage, 'error_details' => $e->getMessage(), 'error_count' => 1 ]); } Log::error('SHJ-9 売上集計処理エラー', [ 'batch_log_id' => $batchLogId, 'exception' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); return [ 'success' => false, 'message' => $errorMessage, 'details' => $e->getMessage(), 'batch_log_id' => $batchLogId ]; } } /** * 【処理1】集計対象を設定する * * @param string $type 集計種別 * @param string $aggregationDate 集計対象日 * @return array 集計対象情報 */ private function setAggregationTarget(string $type, string $aggregationDate): array { $date = Carbon::parse($aggregationDate); switch ($type) { case 'daily': return [ 'type' => 'daily', 'start_date' => $date->format('Y-m-d'), 'end_date' => $date->format('Y-m-d'), 'summary_type' => '日次' ]; case 'monthly': return [ 'type' => 'monthly', 'start_date' => $date->startOfMonth()->format('Y-m-d'), 'end_date' => $date->endOfMonth()->format('Y-m-d'), 'summary_type' => '月次' ]; case 'yearly': return [ 'type' => 'yearly', 'start_date' => $date->startOfYear()->format('Y-m-d'), 'end_date' => $date->endOfYear()->format('Y-m-d'), 'summary_type' => '年次' ]; default: throw new \InvalidArgumentException("不正な集計種別: {$type}"); } } /** * 【処理2】駐輪場マスタを取得する * * 仕様書のSQLクエリに基づく駐輪場情報取得 * SELECT 駐輪場ID, 駐輪場名 * FROM 駐輪場マスタ * WHERE 閉設フラグ <> 1 * ORDER BY 駐輪場ふりがな * * @return array 駐輪場情報 */ private function getParkInformation(): array { try { $parkInfo = DB::table('park') ->select(['park_id', 'park_name']) ->where('park_close_flag', '<>', 1) ->orderBy('park_ruby') ->get() ->toArray(); Log::info('駐輪場マスタ取得完了', [ 'park_count' => count($parkInfo) ]); return $parkInfo; } catch (\Exception $e) { Log::error('駐輪場マスタ取得エラー', [ 'error' => $e->getMessage() ]); throw $e; } } /** * 駐輪場毎の売上集計処理 * * @param object $park 駐輪場情報 * @param array $aggregationTarget 集計対象 * @param string $type 集計種別 * @return int 作成された集計レコード数 */ private function processEarningsForPark($park, array $aggregationTarget, string $type): int { try { // 【処理4】既存の売上集計結果を削除 $this->deleteExistingSummary($park->park_id, $aggregationTarget); // 【処理3】車種区分毎に算出する $psections = $this->getPsectionInformation(); $summaryRecords = 0; foreach ($psections as $psection) { $earningsData = $this->calculateEarningsForPsection( $park->park_id, $psection->psection_id, $aggregationTarget ); if ($this->hasEarningsData($earningsData)) { // 売上集計結果を登録 $this->createEarningsSummary($park, $psection, $aggregationTarget, $earningsData, $type); $summaryRecords++; } } Log::info('駐輪場売上集計完了', [ 'park_id' => $park->park_id, 'park_name' => $park->park_name, 'summary_records' => $summaryRecords ]); return $summaryRecords; } catch (\Exception $e) { Log::error('駐輪場売上集計エラー', [ 'park_id' => $park->park_id, 'error' => $e->getMessage() ]); throw $e; } } /** * 車種区分情報取得 * * @return array 車種区分情報 */ private function getPsectionInformation(): array { return DB::table('psection') ->select(['psection_id', 'psection_subject']) ->get() ->toArray(); } /** * 【処理3】車種区分毎に売上を算出する * * 4つの項目を計算: * ①売上・件数 * ②一時金売上 * ③解約返戻金 * ④再発行金額・件数 * * @param int $parkId 駐輪場ID * @param int $psectionId 車種区分ID * @param array $aggregationTarget 集計対象 * @return array 売上データ */ private function calculateEarningsForPsection(int $parkId, int $psectionId, array $aggregationTarget): array { $startDate = $aggregationTarget['start_date']; $endDate = $aggregationTarget['end_date']; // ①売上・件数(billing_amount) $salesData = DB::table('regular_contract') ->select([ DB::raw('COUNT(*) as sales_count'), DB::raw('COALESCE(SUM(billing_amount), 0) as sales_amount') ]) ->where('park_id', $parkId) ->where('psection_id', $psectionId) ->where('contract_flag', 1) ->whereBetween('contract_payment_day', [$startDate, $endDate]) ->whereNull('contract_cancelday') ->first(); // ②一時金売上(contract_money) $temporaryData = DB::table('regular_contract') ->select([ DB::raw('COUNT(*) as temporary_count'), DB::raw('COALESCE(SUM(contract_money), 0) as temporary_amount') ]) ->where('park_id', $parkId) ->where('psection_id', $psectionId) ->where('contract_flag', 1) ->whereBetween('contract_payment_day', [$startDate, $endDate]) ->whereNotNull('contract_money') ->where('contract_money', '>', 0) ->first(); // ③解約返戻金(refunds) $refundData = DB::table('regular_contract') ->select([ DB::raw('COUNT(*) as refund_count'), DB::raw('COALESCE(SUM(refunds), 0) as refund_amount') ]) ->where('park_id', $parkId) ->where('psection_id', $psectionId) ->whereBetween('contract_cancelday', [$startDate, $endDate]) ->whereNotNull('refunds') ->where('refunds', '>', 0) ->first(); // ④再発行金額・件数(seal_reissue_request) $reissueData = DB::table('regular_contract') ->select([ DB::raw('COUNT(*) as reissue_count'), DB::raw('COALESCE(SUM(contract_seal_issue), 0) as reissue_amount') ]) ->where('park_id', $parkId) ->where('psection_id', $psectionId) ->where('seal_reissue_request', 1) ->whereBetween('updated_at', [$startDate, $endDate]) ->first(); return [ 'sales_count' => $salesData->sales_count ?? 0, 'sales_amount' => $salesData->sales_amount ?? 0, 'temporary_count' => $temporaryData->temporary_count ?? 0, 'temporary_amount' => $temporaryData->temporary_amount ?? 0, 'refund_count' => $refundData->refund_count ?? 0, 'refund_amount' => $refundData->refund_amount ?? 0, 'reissue_count' => $reissueData->reissue_count ?? 0, 'reissue_amount' => $reissueData->reissue_amount ?? 0 ]; } /** * 売上データの存在チェック * * @param array $earningsData 売上データ * @return bool データが存在するかどうか */ private function hasEarningsData(array $earningsData): bool { return $earningsData['sales_count'] > 0 || $earningsData['temporary_count'] > 0 || $earningsData['refund_count'] > 0 || $earningsData['reissue_count'] > 0; } /** * 【処理4】既存の売上集計結果を削除 * * @param int $parkId 駐輪場ID * @param array $aggregationTarget 集計対象 * @return void */ private function deleteExistingSummary(int $parkId, array $aggregationTarget): void { DB::table('earnings_summary') ->where('park_id', $parkId) ->where('summary_start_date', $aggregationTarget['start_date']) ->where('summary_end_date', $aggregationTarget['end_date']) ->delete(); } /** * 売上集計結果を登録 * * @param object $park 駐輪場情報 * @param object $psection 車種区分情報 * @param array $aggregationTarget 集計対象 * @param array $earningsData 売上データ * @param string $type 集計種別 * @return void */ private function createEarningsSummary($park, $psection, array $aggregationTarget, array $earningsData, string $type): void { DB::table('earnings_summary')->insert([ 'park_id' => $park->park_id, 'summary_type' => $aggregationTarget['summary_type'], 'summary_start_date' => $aggregationTarget['start_date'], 'summary_end_date' => $aggregationTarget['end_date'], 'earnings_date' => $aggregationTarget['end_date'], // 集計日として終了日を使用 'psection_id' => $psection->psection_id, 'usertype_subject1' => $psection->psection_subject, 'regular_new_count' => $earningsData['sales_count'], 'regular_new_amount' => $earningsData['sales_amount'], 'turnsum' => $earningsData['temporary_amount'], 'turnsum_count' => $earningsData['temporary_count'], 'refunds' => $earningsData['refund_amount'], 'reissue_count' => $earningsData['reissue_count'], 'reissue_amount' => $earningsData['reissue_amount'], 'summary_note' => "SHJ-9 {$type} 売上集計結果", 'created_at' => now(), 'updated_at' => now(), 'operator_id' => 0 // システム処理 ]); } /** * 【処理5】オペレータキュー作成 * * @param string $message メッセージ * @param int $batchLogId バッチログID * @return void */ private function createOperatorQueue(string $message, int $batchLogId): void { try { DB::table('operator_que')->insert([ 'que_class' => 9, // SHJ-9用のクラス 'user_id' => null, 'contract_id' => null, 'park_id' => null, 'que_comment' => $message, 'que_status' => 1, // 完了 'que_status_comment' => 'バッチ処理完了', 'work_instructions' => "SHJ-9売上集計処理 BatchLogID: {$batchLogId}", 'created_at' => now(), 'updated_at' => now() ]); Log::info('オペレータキュー作成完了', [ 'batch_log_id' => $batchLogId, 'message' => $message ]); } catch (\Exception $e) { Log::error('オペレータキュー作成エラー', [ 'batch_log_id' => $batchLogId, 'error' => $e->getMessage() ]); } } /** * 集計種別のラベル取得 * * @param string $type 集計種別 * @return string ラベル */ private function getTypeLabel(string $type): string { switch ($type) { case 'daily': return '日次'; case 'monthly': return '月次'; case 'yearly': return '年次'; default: return $type; } } }