418 lines
14 KiB
PHP
418 lines
14 KiB
PHP
<?php
|
||
|
||
namespace App\Services;
|
||
|
||
use App\Models\Park;
|
||
use App\Models\RegularContract;
|
||
use App\Models\Device;
|
||
use Illuminate\Support\Facades\Log;
|
||
use Illuminate\Support\Facades\DB;
|
||
use Carbon\Carbon;
|
||
|
||
/**
|
||
* SHJ-4C 室割当処理サービス
|
||
*
|
||
* ゾーン情報取得及び割当処理を実行するビジネスロジック
|
||
* バッチ処理「SHJ-4C室割当」の核となる処理を担当
|
||
*/
|
||
class ShjFourCService
|
||
{
|
||
/**
|
||
* Park モデル
|
||
*
|
||
* @var Park
|
||
*/
|
||
protected $parkModel;
|
||
|
||
/**
|
||
* RegularContract モデル
|
||
*
|
||
* @var RegularContract
|
||
*/
|
||
protected $contractModel;
|
||
|
||
/**
|
||
* ShjEightService
|
||
*
|
||
* @var ShjEightService
|
||
*/
|
||
protected $shjEightService;
|
||
|
||
/**
|
||
* コンストラクタ
|
||
*
|
||
* @param Park $parkModel
|
||
* @param RegularContract $contractModel
|
||
* @param ShjEightService $shjEightService
|
||
*/
|
||
public function __construct(
|
||
Park $parkModel,
|
||
RegularContract $contractModel,
|
||
ShjEightService $shjEightService
|
||
) {
|
||
$this->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
|
||
]);
|
||
|
||
// 実際の事室番号設定ロジックをここに実装
|
||
// 具体的な仕様が必要な場合は後で追加実装
|
||
}
|
||
|
||
} |