Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
41814dd908
commit
e1073e2577
@ -18,12 +18,11 @@ class ShjNineCommand extends Command
|
||||
* コンソールコマンドの名前とシグネチャ
|
||||
*
|
||||
* 引数:
|
||||
* - type: 集計種別 (daily のみ) (必須)
|
||||
* - target_date: 集計対象日 (オプション、YYYY-MM-DD形式)
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'shj:9 {type : 集計種別(daily)} {target_date? : 集計対象日(YYYY-MM-DD)}';
|
||||
protected $signature = 'shj:9 {target_date? : 集計対象日(YYYY-MM-DD)}';
|
||||
|
||||
/**
|
||||
* コンソールコマンドの説明
|
||||
@ -54,137 +53,58 @@ class ShjNineCommand extends Command
|
||||
* コンソールコマンドを実行
|
||||
*
|
||||
* 処理フロー:
|
||||
* 1. パラメータ取得と検証
|
||||
* 2. 集計対象日設定
|
||||
* 3. 売上集計処理実行
|
||||
* 4. バッチログ作成
|
||||
* 5. 処理結果返却
|
||||
* 1. 集計対象日設定(JOB1)
|
||||
* 2. 売上集計処理実行(JOB2~JOB4)
|
||||
* 3. バッチログ作成(JOB5)
|
||||
*
|
||||
* 日付バリデーション・バッチログ作成はService側で実施
|
||||
* ステータスは常にsuccess(式様書準拠)
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
try {
|
||||
// 開始ログ出力
|
||||
$startTime = now();
|
||||
$this->info('SHJ-9 売上集計処理を開始します。');
|
||||
// 開始ログ出力
|
||||
$startTime = now();
|
||||
$this->info('SHJ-9 売上集計処理を開始します。');
|
||||
|
||||
// 引数取得
|
||||
$type = $this->argument('type');
|
||||
$targetDate = $this->argument('target_date');
|
||||
|
||||
Log::info('SHJ-9 売上集計処理開始', [
|
||||
'start_time' => $startTime,
|
||||
'type' => $type,
|
||||
'target_date' => $targetDate
|
||||
]);
|
||||
|
||||
// パラメータ検証
|
||||
if (!$this->validateParameters($type, $targetDate)) {
|
||||
$this->error('パラメータが不正です。');
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
// 集計対象日設定
|
||||
$aggregationDate = $this->determineAggregationDate($type, $targetDate);
|
||||
|
||||
$this->info("集計種別: {$type}");
|
||||
$this->info("集計対象日: {$aggregationDate}");
|
||||
|
||||
// SHJ-9処理実行
|
||||
$result = $this->shjNineService->executeEarningsAggregation($type, $aggregationDate);
|
||||
|
||||
// 処理結果確認
|
||||
if ($result['success']) {
|
||||
$endTime = now();
|
||||
$this->info('SHJ-9 売上集計処理が正常に完了しました。');
|
||||
$this->info("処理時間: {$startTime->diffInSeconds($endTime)}秒");
|
||||
$this->info("処理結果: 駐輪場数 {$result['processed_parks']}, 集計レコード数 {$result['summary_records']}");
|
||||
|
||||
Log::info('SHJ-9 売上集計処理完了', [
|
||||
'end_time' => $endTime,
|
||||
'duration_seconds' => $startTime->diffInSeconds($endTime),
|
||||
'result' => $result
|
||||
]);
|
||||
|
||||
return self::SUCCESS;
|
||||
} else {
|
||||
$this->error('SHJ-9 売上集計処理でエラーが発生しました: ' . $result['message']);
|
||||
Log::error('SHJ-9 売上集計処理エラー', [
|
||||
'error' => $result['message'],
|
||||
'details' => $result['details'] ?? null
|
||||
]);
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->error('SHJ-9 売上集計処理で予期しないエラーが発生しました: ' . $e->getMessage());
|
||||
Log::error('SHJ-9 売上集計処理例外エラー', [
|
||||
'exception' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* パラメータの妥当性を検証
|
||||
*
|
||||
* @param string $type 集計種別
|
||||
* @param string|null $targetDate 対象日
|
||||
* @return bool 検証結果
|
||||
*/
|
||||
private function validateParameters(string $type, ?string $targetDate): bool
|
||||
{
|
||||
// 集計種別チェック(SHJ-9 は日次のみ対応)
|
||||
if ($type !== 'daily') {
|
||||
$this->error('SHJ-9 は日次集計(daily)のみ対応しています。月次/年次は SHJ-10 を使用してください。');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 対象日形式チェック(指定されている場合)
|
||||
if ($targetDate && !$this->isValidDateFormat($targetDate)) {
|
||||
$this->error('対象日の形式が正しくありません(YYYY-MM-DD形式で指定してください)。');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 集計対象日を決定
|
||||
*
|
||||
* @param string $type 集計種別(daily 固定)
|
||||
* @param string|null $targetDate 指定日
|
||||
* @return string 集計対象日
|
||||
*/
|
||||
private function determineAggregationDate(string $type, ?string $targetDate): string
|
||||
{
|
||||
if ($targetDate) {
|
||||
return $targetDate;
|
||||
}
|
||||
// 集計種別は日次固定(SHJ-9は日次のみ)
|
||||
$type = 'daily';
|
||||
|
||||
// 集計対象日設定(JOB1)
|
||||
$targetDate = $this->argument('target_date');
|
||||
// パラメータ指定がない場合は昨日(本日の1日前)
|
||||
return now()->subDay()->format('Y-m-d');
|
||||
}
|
||||
$aggregationDate = $targetDate ?? now()->subDay()->format('Y-m-d');
|
||||
|
||||
/**
|
||||
* 日付形式の検証
|
||||
*
|
||||
* @param string $date 日付文字列
|
||||
* @return bool 有効な日付形式かどうか
|
||||
*/
|
||||
private function isValidDateFormat(string $date): bool
|
||||
{
|
||||
// YYYY-MM-DD形式の正規表現チェック
|
||||
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) {
|
||||
return false;
|
||||
Log::info('SHJ-9 売上集計処理開始', [
|
||||
'start_time' => $startTime,
|
||||
'type' => $type,
|
||||
'target_date' => $aggregationDate
|
||||
]);
|
||||
|
||||
$this->info("集計種別: {$type}");
|
||||
$this->info("集計対象日: {$aggregationDate}");
|
||||
|
||||
// SHJ-9処理実行(日付バリデーション・バッチログ作成含む)
|
||||
$result = $this->shjNineService->executeEarningsAggregation($type, $aggregationDate);
|
||||
|
||||
$endTime = now();
|
||||
$this->info("処理時間: {$startTime->diffInSeconds($endTime)}秒");
|
||||
|
||||
if ($result['success']) {
|
||||
$this->info('SHJ-9 売上集計処理が正常に完了しました。');
|
||||
$this->info("処理結果: 駐輪場数 {$result['processed_parks']}, 集計レコード数 {$result['summary_records']}");
|
||||
} else {
|
||||
$this->info('SHJ-9 売上集計処理が完了しました(エラーあり): ' . $result['message']);
|
||||
}
|
||||
|
||||
// 実際の日付として有効かチェック
|
||||
$dateParts = explode('-', $date);
|
||||
return checkdate((int)$dateParts[1], (int)$dateParts[2], (int)$dateParts[0]);
|
||||
Log::info('SHJ-9 売上集計処理完了', [
|
||||
'end_time' => $endTime,
|
||||
'duration_seconds' => $startTime->diffInSeconds($endTime),
|
||||
'result' => $result
|
||||
]);
|
||||
|
||||
// 式様書準拠: ステータスは常にsuccess
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
116
app/Console/Commands/ShjTwoCommand.php
Normal file
116
app/Console/Commands/ShjTwoCommand.php
Normal file
@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use App\Services\ShjTwoService;
|
||||
|
||||
/**
|
||||
* SHJ-2 データバックアップコマンド
|
||||
*
|
||||
* データベースの夜間自動バックアップを実行する
|
||||
* フルバックアップを5世代保持する
|
||||
* 定期実行(日次 02:45)またはオンデマンド実行
|
||||
*/
|
||||
class ShjTwoCommand extends Command
|
||||
{
|
||||
/**
|
||||
* コンソールコマンドの名前とシグネチャ
|
||||
*
|
||||
* 引数なし - 全自動でバックアップを実行
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'shj:2';
|
||||
|
||||
/**
|
||||
* コンソールコマンドの説明
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'SHJ-2 データバックアップ - データベースとソースコードのフルバックアップを実行(5世代保持)';
|
||||
|
||||
/**
|
||||
* SHJ-2サービスクラス
|
||||
*
|
||||
* @var ShjTwoService
|
||||
*/
|
||||
protected $shjTwoService;
|
||||
|
||||
/**
|
||||
* コンストラクタ
|
||||
*
|
||||
* @param ShjTwoService $shjTwoService
|
||||
*/
|
||||
public function __construct(ShjTwoService $shjTwoService)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->shjTwoService = $shjTwoService;
|
||||
}
|
||||
|
||||
/**
|
||||
* コンソールコマンドを実行
|
||||
*
|
||||
* 処理フロー:
|
||||
* <JOB1> 現時点の世代シフトを行う
|
||||
* <JOB2> フルバックアップを行う
|
||||
* <JOB3> バッチ処理ログを作成する
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
try {
|
||||
// 開始ログ出力
|
||||
$startTime = now();
|
||||
$this->info('SHJ-2 データバックアップ処理を開始します。');
|
||||
|
||||
Log::info('SHJ-2 データバックアップ処理開始', [
|
||||
'start_time' => $startTime
|
||||
]);
|
||||
|
||||
// SHJ-2メイン処理実行
|
||||
$result = $this->shjTwoService->execute();
|
||||
|
||||
$endTime = now();
|
||||
|
||||
// 処理結果に応じた出力
|
||||
if ($result['success']) {
|
||||
$this->info('SHJ-2 データバックアップ処理が正常に完了しました。');
|
||||
$this->info("処理時間: {$startTime->diffInSeconds($endTime)}秒");
|
||||
$this->info("ステータス: {$result['status']}");
|
||||
$this->info("コメント: {$result['status_comment']}");
|
||||
|
||||
Log::info('SHJ-2 データバックアップ処理完了', [
|
||||
'end_time' => $endTime,
|
||||
'duration_seconds' => $startTime->diffInSeconds($endTime),
|
||||
'status' => $result['status'],
|
||||
'status_comment' => $result['status_comment']
|
||||
]);
|
||||
|
||||
return self::SUCCESS;
|
||||
|
||||
} else {
|
||||
$this->error('SHJ-2 データバックアップ処理でエラーが発生しました: ' . $result['status_comment']);
|
||||
|
||||
Log::error('SHJ-2 データバックアップ処理エラー', [
|
||||
'status' => $result['status'],
|
||||
'status_comment' => $result['status_comment']
|
||||
]);
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->error('SHJ-2 データバックアップ処理で予期しないエラーが発生しました: ' . $e->getMessage());
|
||||
|
||||
Log::error('SHJ-2 データバックアップ処理例外エラー', [
|
||||
'exception' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -38,6 +38,7 @@ class OperatorQue extends Model
|
||||
11 => '名寄せフリガナ照合エラー',
|
||||
12 => '本人確認(減免更新)',
|
||||
13 => '本人確認(学生更新)',
|
||||
14 => '集計対象エラー',
|
||||
101 => 'サーバーエラー',
|
||||
102 => 'プリンタエラー',
|
||||
103 => 'スキャナーエラー',
|
||||
|
||||
@ -29,6 +29,11 @@ class ShjNineService
|
||||
*/
|
||||
const SUMMARY_TYPE_DAILY = 3;
|
||||
|
||||
/**
|
||||
* 情報不備なしの固定文字列(全角スペース付き)
|
||||
*/
|
||||
const DATA_INTEGRITY_NONE = ' 情報不備:なし';
|
||||
|
||||
/**
|
||||
* ShjEightService インスタンス
|
||||
*
|
||||
@ -65,24 +70,27 @@ class ShjNineService
|
||||
public function executeEarningsAggregation(string $type, string $aggregationDate): array
|
||||
{
|
||||
$statusComments = []; // 内部変数.ステータスコメント
|
||||
$dataIntegrityIssues = []; // 内部変数.情報不備
|
||||
$allDataIntegrity = []; // 内部変数.情報不備(なし含む全件)
|
||||
$dataIntegrityIssues = []; // オペレータキュー作成用(NULL以外)
|
||||
|
||||
try {
|
||||
// 【処理1】集計対象を設定する
|
||||
// パラメーター検証(日付形式チェック)
|
||||
if (!$this->isValidDateFormat($aggregationDate)) {
|
||||
// 日付形式エラー時は【処理5】へ(warning扱い)
|
||||
// 日付形式エラー時は【処理5】へ(仕様JOB1:ステータスコメント設定→JOB5)
|
||||
$statusComment = "売上集計(日次):パラメーターが不正です。(日付形式ではありません)";
|
||||
|
||||
// 【処理5】オペレータキュー作成
|
||||
$this->createOperatorQueue($statusComment, null);
|
||||
// 仕様JOB5:情報不備がNULL→オペレータキュー作成しない(日付エラーは情報不備ではない)
|
||||
|
||||
// SHJ-8 バッチ処理ログ作成
|
||||
$this->callShjEight('SHJ-9売上集計(日次)', 'success', $statusComment);
|
||||
// SHJ-8 バッチ処理ログ作成(仕様JOB5:ステータスは常にsuccess)
|
||||
$shjEightResult = $this->callShjEight('SHJ-9売上集計(日次)', 'success', $statusComment);
|
||||
$this->evaluateShjEightResult($shjEightResult);
|
||||
|
||||
return [
|
||||
'success' => true, // 仕様上はwarningで成功扱い
|
||||
'message' => $statusComment
|
||||
'message' => $statusComment,
|
||||
'processed_parks' => 0,
|
||||
'summary_records' => 0
|
||||
];
|
||||
}
|
||||
|
||||
@ -99,13 +107,12 @@ class ShjNineService
|
||||
// 【判断1】取得件数判定
|
||||
if (empty($parkInfo)) {
|
||||
$statusComment = '売上集計(日次):駐輪場マスタが存在していません。';
|
||||
$statusComments[] = $statusComment;
|
||||
|
||||
// 【処理5】オペレータキュー作成
|
||||
$this->createOperatorQueue($statusComment, null);
|
||||
// 仕様JOB2-STEP1:情報不備がNULL→オペレータキュー作成しない
|
||||
|
||||
// SHJ-8 バッチ処理ログ作成
|
||||
$this->callShjEight('SHJ-9売上集計(日次)', 'success', $statusComment);
|
||||
// SHJ-8 バッチ処理ログ作成(仕様JOB5:ステータスは常にsuccess)
|
||||
$shjEightResult = $this->callShjEight('SHJ-9売上集計(日次)', 'success', $statusComment);
|
||||
$this->evaluateShjEightResult($shjEightResult);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
@ -115,7 +122,7 @@ class ShjNineService
|
||||
];
|
||||
}
|
||||
|
||||
// 【処理3】車種区分毎に算出する & 【処理4】売上集計結果を削除→登録する
|
||||
// 仕様JOB3/JOB4:車種区分毎に算出→売上集計結果を登録
|
||||
$summaryRecords = 0;
|
||||
$processedParks = 0;
|
||||
|
||||
@ -125,39 +132,53 @@ class ShjNineService
|
||||
$processedParks++;
|
||||
$summaryRecords += $result['summary_records'];
|
||||
|
||||
// 対象データなしの場合のステータスコメント収集
|
||||
// 仕様JOB4:レコード毎のステータスコメントを収集
|
||||
if (!empty($result['status_comments'])) {
|
||||
$statusComments = array_merge($statusComments, $result['status_comments']);
|
||||
}
|
||||
|
||||
// 仕様JOB3-STEP1:対象データなしの場合のステータスコメント
|
||||
if (!empty($result['no_data_message'])) {
|
||||
$statusComments[] = $result['no_data_message'];
|
||||
}
|
||||
|
||||
// 情報不備を収集("なし"でない場合)
|
||||
if ($result['data_integrity_issue'] !== '情報不備:なし') {
|
||||
$dataIntegrityIssues[] = $result['data_integrity_issue'];
|
||||
// 仕様JOB5:全parkの情報不備を収集(なし含む)
|
||||
$allDataIntegrity[] = $result['data_integrity_issue'];
|
||||
|
||||
// 仕様JOB5:NULL以外の場合にオペレータキューを登録
|
||||
if ($result['data_integrity_issue'] !== null) {
|
||||
$dataIntegrityIssues[] = [
|
||||
'park_id' => $park->park_id,
|
||||
'message' => $result['data_integrity_issue']
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// 最終ステータスコメント生成
|
||||
$finalStatusComment = "売上集計(日次):対象日={$targetDate}、駐輪場数={$processedParks}、集計レコード数={$summaryRecords}";
|
||||
if (!empty($dataIntegrityIssues)) {
|
||||
$finalStatusComment .= "、情報不備=" . implode('、', $dataIntegrityIssues);
|
||||
// 仕様JOB5:情報不備がNULL以外の場合、オペレータキューを登録
|
||||
foreach ($dataIntegrityIssues as $issue) {
|
||||
$this->createOperatorQueue($issue['message'], $issue['park_id']);
|
||||
}
|
||||
|
||||
// 【処理5】オペレータキュー作成
|
||||
// ※ 駐輪場単位で既に作成済み(processEarningsForPark内で情報不備検出時に実施)
|
||||
if (!empty($dataIntegrityIssues)) {
|
||||
Log::warning('SHJ-9 情報不備検出', [
|
||||
'issues' => $dataIntegrityIssues
|
||||
]);
|
||||
}
|
||||
|
||||
// SHJ-8 バッチ処理ログ作成
|
||||
$this->callShjEight('SHJ-9売上集計(日次)', 'success', $finalStatusComment);
|
||||
// 仕様JOB5:ステータスコメント = 内部変数.ステータスコメント + 内部変数.情報不備(なし含む全件)
|
||||
$allParts = array_merge($statusComments, $allDataIntegrity);
|
||||
$finalStatusComment = implode("\n", $allParts);
|
||||
|
||||
Log::info('SHJ-9 完全ステータスコメント', ['status_comment' => $finalStatusComment]);
|
||||
|
||||
// 仕様JOB5:SHJ-8 バッチ処理ログ作成(ステータスは常にsuccess)
|
||||
$shjEightResult = $this->callShjEight('SHJ-9売上集計(日次)', 'success', $finalStatusComment);
|
||||
$this->evaluateShjEightResult($shjEightResult);
|
||||
|
||||
Log::info('SHJ-9 売上集計処理完了', [
|
||||
'processed_parks' => $processedParks,
|
||||
'summary_records' => $summaryRecords,
|
||||
'data_integrity_issues' => count($dataIntegrityIssues),
|
||||
'no_data_parks' => count($statusComments)
|
||||
'data_integrity_issues' => count($dataIntegrityIssues)
|
||||
]);
|
||||
|
||||
return [
|
||||
@ -173,7 +194,8 @@ class ShjNineService
|
||||
|
||||
// SHJ-8 バッチ処理ログ作成(エラー時も作成)
|
||||
try {
|
||||
$this->callShjEight('SHJ-9売上集計(日次)', 'error', $errorMessage);
|
||||
$shjEightResult = $this->callShjEight('SHJ-9売上集計(日次)', 'success', $errorMessage);
|
||||
$this->evaluateShjEightResult($shjEightResult);
|
||||
} catch (\Exception $shjException) {
|
||||
Log::error('SHJ-8呼び出しエラー', ['error' => $shjException->getMessage()]);
|
||||
}
|
||||
@ -192,19 +214,21 @@ class ShjNineService
|
||||
}
|
||||
|
||||
/**
|
||||
* 日付形式の検証
|
||||
* 日付形式の検証(厳格:YYYY-MM-DD形式のみ許可)
|
||||
*
|
||||
* @param string $date 日付文字列
|
||||
* @return bool 有効な日付形式かどうか
|
||||
*/
|
||||
private function isValidDateFormat(string $date): bool
|
||||
{
|
||||
try {
|
||||
$parsed = Carbon::parse($date);
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
// YYYY-MM-DD形式の正規表現チェック
|
||||
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 実際の日付として有効かチェック
|
||||
$dateParts = explode('-', $date);
|
||||
return checkdate((int)$dateParts[1], (int)$dateParts[2], (int)$dateParts[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -242,60 +266,56 @@ class ShjNineService
|
||||
*
|
||||
* @param object $park 駐輪場情報
|
||||
* @param string $targetDate 集計対象日(YYYY-MM-DD)
|
||||
* @return array 処理結果 ['summary_records' => int, 'data_integrity_issue' => string, 'no_data_message' => string|null]
|
||||
* @return array 処理結果
|
||||
*/
|
||||
private function processEarningsForPark($park, string $targetDate): array
|
||||
{
|
||||
try {
|
||||
// 0. 情報不備チェック
|
||||
// 仕様JOB3-0:情報不備チェック(SQL-2)
|
||||
$dataIntegrityIssue = $this->checkDataIntegrity($park->park_id, $targetDate);
|
||||
|
||||
// 情報不備がある場合、駐輪場単位でオペレータキュー作成(仕様 todo/SHJ-9/SHJ-9.txt:253-263)
|
||||
if ($dataIntegrityIssue !== '情報不備:なし') {
|
||||
$this->createOperatorQueue($dataIntegrityIssue, $park->park_id);
|
||||
}
|
||||
|
||||
// ① 定期契約データ取得(車種区分・分類名1・定期有効月数毎)
|
||||
// 仕様JOB3-①:定期契約データ取得(車種区分・分類名1・定期有効月数毎)
|
||||
$regularData = $this->calculateRegularEarnings($park->park_id, $targetDate);
|
||||
|
||||
// ② 一時金データ取得(車種毎)
|
||||
// 仕様JOB3-②:一時金データ取得(車種毎)
|
||||
$lumpsumData = $this->calculateLumpsumEarnings($park->park_id, $targetDate);
|
||||
|
||||
// ③ 解約返戻金データ取得(車種区分毎)
|
||||
// 仕様JOB3-③:解約返戻金データ取得(車種区分毎)
|
||||
$refundData = $this->calculateRefundEarnings($park->park_id, $targetDate);
|
||||
|
||||
// ④ 再発行データ取得(車種区分毎)
|
||||
// 仕様JOB3-④:再発行データ取得(車種区分毎)
|
||||
$reissueData = $this->calculateReissueCount($park->park_id, $targetDate);
|
||||
|
||||
// 【判断2】データがいずれかあれば【処理4】へ
|
||||
// 仕様JOB3-STEP1:①②③④のデータがいずれかあれば→JOB4へ
|
||||
if (empty($regularData) && empty($lumpsumData) && empty($refundData) && empty($reissueData)) {
|
||||
// 対象データなし - 仕様 todo/SHJ-9/SHJ-9.txt:172-175
|
||||
// 仕様JOB3-STEP1:対象データなし
|
||||
$noDataMessage = "売上集計(日次):対象日:{$targetDate}/駐輪場:{$park->park_name}:売上データが存在しません。";
|
||||
|
||||
return [
|
||||
'summary_records' => 0,
|
||||
'data_integrity_issue' => $dataIntegrityIssue,
|
||||
'no_data_message' => $noDataMessage
|
||||
'no_data_message' => $noDataMessage,
|
||||
'status_comments' => []
|
||||
];
|
||||
}
|
||||
|
||||
// 【処理4】既存の売上集計結果を削除
|
||||
$this->deleteExistingSummary($park->park_id, $targetDate);
|
||||
|
||||
// 【処理4】売上集計結果を登録
|
||||
// 仕様JOB4:売上集計結果を登録(同一キーの既存レコードは各insert前に削除)
|
||||
$summaryRecords = 0;
|
||||
$statusComments = [];
|
||||
|
||||
// ① 定期契約データがある場合:同じ組合せ(psection×usertype×months)を統合
|
||||
// ① 定期契約データ:同じ組合せ(psection×usertype×months)を統合
|
||||
$mergedRegularData = $this->mergeRegularDataByGroup($regularData);
|
||||
foreach ($mergedRegularData as $key => $mergedRow) {
|
||||
$this->createEarningsSummary($park, $mergedRow, $targetDate, 'regular');
|
||||
$summaryRecords++;
|
||||
}
|
||||
$sc = $this->createEarningsSummary($park, $mergedRow, $targetDate, 'regular', $dataIntegrityIssue);
|
||||
$statusComments[] = $sc;
|
||||
$summaryRecords++;
|
||||
}
|
||||
|
||||
// ②③④ 一時金・解約・再発行データがある場合(車種区分毎に集約)
|
||||
// ②③④ 一時金・解約・再発行データ(車種区分毎に集約)
|
||||
$otherDataByPsection = $this->mergeOtherEarningsData($lumpsumData, $refundData, $reissueData);
|
||||
foreach ($otherDataByPsection as $psectionId => $data) {
|
||||
$this->createEarningsSummary($park, $data, $targetDate, 'other');
|
||||
$sc = $this->createEarningsSummary($park, $data, $targetDate, 'other', null);
|
||||
$statusComments[] = $sc;
|
||||
$summaryRecords++;
|
||||
}
|
||||
|
||||
@ -309,7 +329,8 @@ class ShjNineService
|
||||
return [
|
||||
'summary_records' => $summaryRecords,
|
||||
'data_integrity_issue' => $dataIntegrityIssue,
|
||||
'no_data_message' => null
|
||||
'no_data_message' => null,
|
||||
'status_comments' => $statusComments
|
||||
];
|
||||
|
||||
} catch (\Exception $e) {
|
||||
@ -333,8 +354,9 @@ class ShjNineService
|
||||
*/
|
||||
private function checkDataIntegrity(int $parkId, string $targetDate): string
|
||||
{
|
||||
// 仕様SQL-2:情報不備チェック(ログ吐き出し)
|
||||
$incompleteContracts = DB::table('regular_contract')
|
||||
->select('contract_id')
|
||||
->select(['contract_id', 'contract_reduction', 'update_flag', 'psection_id', 'enable_months'])
|
||||
->where('park_id', $parkId)
|
||||
->where('contract_flag', 1)
|
||||
->whereDate('contract_payment_day', '=', $targetDate)
|
||||
@ -343,15 +365,26 @@ class ShjNineService
|
||||
->orWhereNull('psection_id')
|
||||
->orWhereNull('enable_months');
|
||||
})
|
||||
->pluck('contract_id')
|
||||
->get()
|
||||
->toArray();
|
||||
|
||||
if (empty($incompleteContracts)) {
|
||||
return '情報不備:なし';
|
||||
return self::DATA_INTEGRITY_NONE;
|
||||
}
|
||||
|
||||
// 仕様フォーマット:"情報不備:" + 契約IDカンマ区切り
|
||||
return '情報不備:' . implode(',', $incompleteContracts);
|
||||
// 仕様SQL-2:ログ吐き出し(詳細情報を出力)
|
||||
Log::warning('SHJ-9 情報不備契約詳細', [
|
||||
'park_id' => $parkId,
|
||||
'target_date' => $targetDate,
|
||||
'contracts' => $incompleteContracts
|
||||
]);
|
||||
|
||||
// 仕様フォーマット:" 情報不備:" + 契約IDカンマ区切り(全角スペース付き)
|
||||
$contractIds = array_map(function($c) {
|
||||
return is_object($c) ? $c->contract_id : $c['contract_id'];
|
||||
}, $incompleteContracts);
|
||||
|
||||
return ' 情報不備:' . implode(',', $contractIds);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -611,66 +644,92 @@ class ShjNineService
|
||||
}
|
||||
|
||||
/**
|
||||
* 【処理4】既存の売上集計結果を削除
|
||||
* 車種区分名を取得
|
||||
*
|
||||
* 仕様書のキー:駐輪場ID, 集計種別, 集計開始日, 集計終了日, 売上日付, 車種区分, 分類名1, 定期有効月数
|
||||
* 仕様 todo/SHJ-9/SHJ-9.txt:181-189
|
||||
*
|
||||
* @param int $parkId 駐輪場ID
|
||||
* @param string $targetDate 集計対象日(YYYY-MM-DD)
|
||||
* @return void
|
||||
* @param int|null $psectionId 車種区分ID
|
||||
* @return string 車種区分名
|
||||
*/
|
||||
private function deleteExistingSummary(int $parkId, string $targetDate): void
|
||||
private function getPsectionName(?int $psectionId): string
|
||||
{
|
||||
// 仕様書どおり、同一キーの組み合わせで削除
|
||||
// 日次の場合、集計開始日・終了日はNULL、売上日付で判定
|
||||
DB::table('earnings_summary')
|
||||
->where('park_id', $parkId)
|
||||
->where('summary_type', self::SUMMARY_TYPE_DAILY)
|
||||
->whereNull('summary_start_date')
|
||||
->whereNull('summary_end_date')
|
||||
->where('earnings_date', $targetDate)
|
||||
// psection_id, usertype_subject1, enable_months は
|
||||
// レコードごとに異なるため、ここでは指定しない
|
||||
->delete();
|
||||
if ($psectionId === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
Log::debug('既存の売上集計結果削除', [
|
||||
'park_id' => $parkId,
|
||||
'target_date' => $targetDate
|
||||
]);
|
||||
$name = DB::table('psection')
|
||||
->where('psection_id', $psectionId)
|
||||
->value('psection_subject');
|
||||
|
||||
return $name ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 売上集計結果を登録
|
||||
* 仕様JOB4:売上集計結果を削除→登録
|
||||
*
|
||||
* 仕様 todo/SHJ-9/SHJ-9.txt:181-247
|
||||
* 同一キー(駐車場ID,集計種別,集計開始日,集計終了日,売上日付,車種区分,分類名1,定期有効月数)
|
||||
* が既に登録済みの場合削除した上で、新規レコードを登録する。
|
||||
*
|
||||
* @param object $park 駐輪場情報
|
||||
* @param object $data 売上データ
|
||||
* @param string $targetDate 集計対象日(YYYY-MM-DD)
|
||||
* @param string $dataType データ種別(regular or other)
|
||||
* @return void
|
||||
* @param string|null $dataIntegrityIssue 情報不備メッセージ(regularのみ使用)
|
||||
* @return string 仕様JOB4形式のステータスコメント
|
||||
*/
|
||||
private function createEarningsSummary($park, $data, string $targetDate, string $dataType): void
|
||||
private function createEarningsSummary($park, $data, string $targetDate, string $dataType, ?string $dataIntegrityIssue = null): string
|
||||
{
|
||||
$usertypeSubject1 = $data->usertype_subject1 ?? null;
|
||||
$enableMonths = $data->enable_months ?? 0;
|
||||
|
||||
// 仕様JOB4:同一キーの既存レコードを削除
|
||||
$deleteQuery = DB::table('earnings_summary')
|
||||
->where('park_id', $park->park_id)
|
||||
->where('summary_type', self::SUMMARY_TYPE_DAILY)
|
||||
->whereNull('summary_start_date')
|
||||
->whereNull('summary_end_date')
|
||||
->where('earnings_date', $targetDate);
|
||||
|
||||
// NULL値対応:psection_idがnullの場合はwhereNull
|
||||
if ($data->psection_id === null) {
|
||||
$deleteQuery->whereNull('psection_id');
|
||||
} else {
|
||||
$deleteQuery->where('psection_id', $data->psection_id);
|
||||
}
|
||||
|
||||
// NULL値対応:usertype_subject1がnullの場合はwhereNull
|
||||
if ($usertypeSubject1 === null) {
|
||||
$deleteQuery->whereNull('usertype_subject1');
|
||||
} else {
|
||||
$deleteQuery->where('usertype_subject1', $usertypeSubject1);
|
||||
}
|
||||
$deleteQuery->where('enable_months', $enableMonths);
|
||||
$deleteQuery->delete();
|
||||
|
||||
// 仕様:集計備考の生成
|
||||
if ($dataType === 'regular' && $dataIntegrityIssue !== null) {
|
||||
// 仕様JOB3-①:定期データのsummary_noteには情報不備を含める
|
||||
$summaryNote = "SHJ-9:{$targetDate}{$dataIntegrityIssue}";
|
||||
} else {
|
||||
// 仕様JOB3-②③④:otherデータのsummary_note
|
||||
$summaryNote = "SHJ-9:{$targetDate}";
|
||||
}
|
||||
|
||||
$insertData = [
|
||||
'park_id' => $park->park_id,
|
||||
'summary_type' => self::SUMMARY_TYPE_DAILY, // 3 = 日次
|
||||
'summary_start_date' => null, // 日次は NULL (仕様line 215)
|
||||
'summary_end_date' => null, // 日次は NULL (仕様line 216)
|
||||
'summary_type' => self::SUMMARY_TYPE_DAILY,
|
||||
'summary_start_date' => null,
|
||||
'summary_end_date' => null,
|
||||
'earnings_date' => $targetDate,
|
||||
'psection_id' => $data->psection_id,
|
||||
'usertype_subject1' => $data->usertype_subject1 ?? null, // 実際の分類名
|
||||
'enable_months' => $data->enable_months ?? 0, // 実際の定期有効月数
|
||||
'summary_note' => "SHJ-9:{$targetDate}", // 仕様line 236
|
||||
'usertype_subject1' => $usertypeSubject1,
|
||||
'enable_months' => $enableMonths,
|
||||
'summary_note' => $summaryNote,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
'operator_id' => self::BATCH_OPERATOR_ID // 9999999 (仕様line 239)
|
||||
'operator_id' => self::BATCH_OPERATOR_ID
|
||||
];
|
||||
|
||||
if ($dataType === 'regular') {
|
||||
// 定期契約データの場合:mergeRegularDataByGroup()で既に統合済み
|
||||
// 新規/更新 × 減免/通常 の各件数・金額がすべて含まれている (仕様line 98-113)
|
||||
// 仕様JOB3-①:定期契約データ
|
||||
$insertData = array_merge($insertData, [
|
||||
'regular_new_count' => $data->regular_new_count ?? 0,
|
||||
'regular_new_amount' => $data->regular_new_amount ?? 0,
|
||||
@ -680,43 +739,50 @@ class ShjNineService
|
||||
'regular_update_amount' => $data->regular_update_amount ?? 0,
|
||||
'regular_update_reduction_count' => $data->regular_update_reduction_count ?? 0,
|
||||
'regular_update_reduction_amount' => $data->regular_update_reduction_amount ?? 0,
|
||||
'lumpsum_count' => 0, // 仕様line 106
|
||||
'lumpsum' => 0, // 仕様line 107
|
||||
'refunds' => 0, // 仕様line 108
|
||||
'other_income' => 0, // 仕様line 109
|
||||
'other_spending' => 0, // 仕様line 110
|
||||
'reissue_count' => 0, // 仕様line 111
|
||||
'reissue_amount' => 0 // 仕様line 112
|
||||
'lumpsum_count' => 0,
|
||||
'lumpsum' => 0,
|
||||
'refunds' => 0,
|
||||
'other_income' => 0,
|
||||
'other_spending' => 0,
|
||||
'reissue_count' => 0,
|
||||
'reissue_amount' => 0
|
||||
]);
|
||||
} else {
|
||||
// 一時金・解約・再発行データの場合:定期フィールドは0固定 (仕様line 152-167)
|
||||
// 仕様JOB3-②③④:一時金・解約・再発行データ(定期フィールドは0固定)
|
||||
$insertData = array_merge($insertData, [
|
||||
'regular_new_count' => 0, // 仕様line 152
|
||||
'regular_new_amount' => 0, // 仕様line 153
|
||||
'regular_new_reduction_count' => 0, // 仕様line 154
|
||||
'regular_new_reduction_amount' => 0, // 仕様line 155
|
||||
'regular_update_count' => 0, // 仕様line 156
|
||||
'regular_update_amount' => 0, // 仕様line 157
|
||||
'regular_update_reduction_count' => 0, // 仕様line 158
|
||||
'regular_update_reduction_amount' => 0, // 仕様line 159
|
||||
'lumpsum_count' => $data->lumpsum_count ?? 0, // 仕様line 160
|
||||
'lumpsum' => $data->lumpsum ?? 0, // 仕様line 161
|
||||
'refunds' => $data->refunds ?? 0, // 仕様line 162
|
||||
'other_income' => 0, // 仕様line 163
|
||||
'other_spending' => 0, // 仕様line 164
|
||||
'reissue_count' => $data->reissue_count ?? 0, // 仕様line 165
|
||||
'reissue_amount' => 0 // 仕様line 166: 0固定
|
||||
'regular_new_count' => 0,
|
||||
'regular_new_amount' => 0,
|
||||
'regular_new_reduction_count' => 0,
|
||||
'regular_new_reduction_amount' => 0,
|
||||
'regular_update_count' => 0,
|
||||
'regular_update_amount' => 0,
|
||||
'regular_update_reduction_count' => 0,
|
||||
'regular_update_reduction_amount' => 0,
|
||||
'lumpsum_count' => $data->lumpsum_count ?? 0,
|
||||
'lumpsum' => $data->lumpsum ?? 0,
|
||||
'refunds' => $data->refunds ?? 0,
|
||||
'other_income' => 0,
|
||||
'other_spending' => 0,
|
||||
'reissue_count' => $data->reissue_count ?? 0,
|
||||
'reissue_amount' => 0
|
||||
]);
|
||||
}
|
||||
|
||||
DB::table('earnings_summary')->insert($insertData);
|
||||
|
||||
// 仕様JOB4:レコード毎のステータスコメント生成(全データ型共通フォーマット)
|
||||
$psectionName = $this->getPsectionName($data->psection_id);
|
||||
$displayUsertype = $usertypeSubject1 ?? '';
|
||||
$statusComment = "売上集計(日次):対象日:{$targetDate}/駐輪場:{$park->park_name}/車種区分:{$psectionName}/分類名1:{$displayUsertype}/定期有効月数:{$enableMonths}";
|
||||
|
||||
Log::debug('売上集計結果登録', [
|
||||
'park_id' => $park->park_id,
|
||||
'psection_id' => $data->psection_id,
|
||||
'data_type' => $dataType,
|
||||
'target_date' => $targetDate
|
||||
]);
|
||||
|
||||
return $statusComment;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -777,9 +843,9 @@ class ShjNineService
|
||||
* @param string $jobName ジョブ名
|
||||
* @param string $status ステータス (success/error)
|
||||
* @param string $statusComment 業務固有のステータスコメント
|
||||
* @return void
|
||||
* @return array SHJ-8実行結果
|
||||
*/
|
||||
private function callShjEight(string $jobName, string $status, string $statusComment): void
|
||||
private function callShjEight(string $jobName, string $status, string $statusComment): array
|
||||
{
|
||||
try {
|
||||
$device = Device::orderBy('device_id')->first();
|
||||
@ -787,7 +853,7 @@ class ShjNineService
|
||||
|
||||
$today = now()->format('Y/m/d');
|
||||
|
||||
$this->shjEightService->execute(
|
||||
$result = $this->shjEightService->execute(
|
||||
$deviceId,
|
||||
'SHJ-9',
|
||||
$jobName,
|
||||
@ -799,9 +865,12 @@ class ShjNineService
|
||||
|
||||
Log::info('SHJ-8 バッチ処理ログ作成完了', [
|
||||
'job_name' => $jobName,
|
||||
'status' => $status
|
||||
'status' => $status,
|
||||
'shj8_result' => $result['result'] ?? null
|
||||
]);
|
||||
|
||||
return $result;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('SHJ-8 バッチ処理ログ作成エラー', [
|
||||
'error' => $e->getMessage(),
|
||||
@ -811,4 +880,24 @@ class ShjNineService
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SHJ-8実行結果の判定
|
||||
*
|
||||
* 仕様JOB5の判定(処理結果=0/その他)をログで明示する。
|
||||
*
|
||||
* @param array $shjEightResult SHJ-8返却値
|
||||
* @return void
|
||||
*/
|
||||
private function evaluateShjEightResult(array $shjEightResult): void
|
||||
{
|
||||
if ((int)($shjEightResult['result'] ?? 1) === 0) {
|
||||
Log::info('SHJ-9 SHJ-8処理結果判定: 0(正常)');
|
||||
return;
|
||||
}
|
||||
|
||||
Log::warning('SHJ-9 SHJ-8処理結果判定: 0以外(異常)', [
|
||||
'shj8_result' => $shjEightResult
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -201,63 +201,40 @@ class ShjSixService
|
||||
}
|
||||
|
||||
// 【処理5】バッチ処理ログを作成する - SHJ-8呼び出し
|
||||
// 仕様書準拠:キュー登録件数も含める
|
||||
$statusComment = sprintf(
|
||||
'デバイス数: %d, アラート件数: %d, メール正常: %d件, メール異常: %d件, キュー登録正常: %d件, キュー登録異常: %d件',
|
||||
count($devices),
|
||||
$alertCount,
|
||||
$totalMailSuccessCount,
|
||||
$totalMailErrorCount,
|
||||
$totalQueueSuccessCount,
|
||||
$totalQueueErrorCount
|
||||
);
|
||||
|
||||
// バッチコメントが累積されている場合は追加
|
||||
if (!empty($accumulatedBatchComment)) {
|
||||
$statusComment .= ' | ' . $accumulatedBatchComment;
|
||||
}
|
||||
// 仕様書準拠:ステータスコメント = バッチコメント + 各件数
|
||||
$statusComment = $accumulatedBatchComment
|
||||
. ':メール正常終了件数' . $totalMailSuccessCount
|
||||
. '、メール異常終了件数' . $totalMailErrorCount
|
||||
. '、キュー登録正常終了件数' . $totalQueueSuccessCount
|
||||
. '、キュー登録異常終了件数' . $totalQueueErrorCount;
|
||||
|
||||
$status = 'success'; // 仕様書:常に"success"で記録
|
||||
$message = empty($warnings) ?
|
||||
'SHJ-6 サーバ死活監視処理正常完了' :
|
||||
'SHJ-6 サーバ死活監視処理完了(警告あり)';
|
||||
|
||||
// 仕様書準拠:処理2で取得したデバイスIDを連結
|
||||
// 仕様書準拠:JOB5 は常に実行する(JOB4 の結果に関わらず)
|
||||
// 注:bat_job_log.device_id は int unsigned のため連結文字列は格納不可
|
||||
// 先頭デバイスIDを使用。デバイスなしの場合は 0
|
||||
// (SHJ-8 はエラー返却するが、式様書上 result≠0 でも「処理を終了する」のため問題なし)
|
||||
$deviceIds = $devices->pluck('device_id')->toArray();
|
||||
$concatenatedDeviceIds = !empty($deviceIds) ? implode(',', $deviceIds) : '';
|
||||
$deviceId = !empty($deviceIds) ? (int)$deviceIds[0] : 0;
|
||||
|
||||
// SHJ-8 バッチ処理ログ作成(仕様書準拠)
|
||||
// device_id = 処理2で取得したデバイスID(複数なら連結)
|
||||
// process_name = 処理4のプロセス名(プリンタログから取得、なければ'SHJ-6')
|
||||
// job_name = "SHJ-6サーバ死活監視" 固定
|
||||
// status = 常に "success"
|
||||
$shj8Result = $this->createShjBatchLog([
|
||||
'device_id' => $concatenatedDeviceIds, // 処理2のデバイスID(連結)
|
||||
'process_name' => $printerResult['process_name'] ?? 'SHJ-6', // 処理4のプロセス名
|
||||
'job_name' => 'SHJ-6サーバ死活監視',
|
||||
'status' => 'success', // 仕様書:常にsuccess
|
||||
'status_comment' => $statusComment,
|
||||
'mail_success_count' => $totalMailSuccessCount,
|
||||
'mail_error_count' => $totalMailErrorCount,
|
||||
'queue_success_count' => $totalQueueSuccessCount, // 仕様書準拠
|
||||
'queue_error_count' => $totalQueueErrorCount, // 仕様書準拠
|
||||
'device_count' => count($devices),
|
||||
'alert_count' => $alertCount
|
||||
]);
|
||||
// SHJ-8 バッチ処理ログ作成
|
||||
$shj8Result = $this->createShjBatchLog(
|
||||
$deviceId,
|
||||
$printerResult['process_name'] ?? null, // 仕様書準拠:JOB4のプロセス名のみ
|
||||
'SHJ-6サーバ死活監視', // job_name 固定
|
||||
'success', // 仕様書:常にsuccess
|
||||
$statusComment
|
||||
);
|
||||
|
||||
// 仕様書準拠:SHJ-8の戻り値確認(処理結果 = 0以外なら異常)
|
||||
// 式様書:result=0 でも result≠0 でも「処理を終了する」
|
||||
if (!$shj8Result['success']) {
|
||||
$shj8Error = $shj8Result['error'] ?? 'Unknown error';
|
||||
Log::warning('SHJ-8 バッチ処理ログ作成で異常が発生しました', [
|
||||
'error' => $shj8Error
|
||||
Log::warning('SHJ-8 バッチ処理ログ作成で異常', [
|
||||
'device_id' => $deviceId,
|
||||
'error' => $shj8Result['error'] ?? ''
|
||||
]);
|
||||
|
||||
// 仕様書準拠:異常時はバッチコメントへ反映
|
||||
$accumulatedBatchComment .= ($accumulatedBatchComment ? ' | ' : '') .
|
||||
sprintf('SHJ-8異常: %s', $shj8Error);
|
||||
|
||||
// statusCommentも更新
|
||||
$statusComment .= sprintf(' | SHJ-8異常: %s', $shj8Error);
|
||||
}
|
||||
|
||||
Log::info('SHJ-6 サーバ死活監視処理完了', [
|
||||
@ -378,16 +355,14 @@ class ShjSixService
|
||||
private function getServerDevices()
|
||||
{
|
||||
try {
|
||||
// 仕様書準拠SQL-2:デバイス管理マスタを取得
|
||||
$devices = DB::table('device')
|
||||
->select([
|
||||
'device_id',
|
||||
'park_id',
|
||||
'device_type',
|
||||
'device_subject',
|
||||
'device_identifier',
|
||||
'device_work',
|
||||
'device_workstart',
|
||||
'device_remarks'
|
||||
])
|
||||
->where('device_type', self::DEVICE_TYPE_SERVER)
|
||||
->where('device_work', self::DEVICE_WORK_ACTIVE)
|
||||
@ -433,7 +408,7 @@ class ShjSixService
|
||||
->orderBy('created_at', 'desc')
|
||||
->first();
|
||||
|
||||
// ログが取得できない、または異常状態の場合
|
||||
// 仕様書準拠:JOB3-STEP1 取得レコードが0件の場合のみアラート
|
||||
if (!$latestLog) {
|
||||
// ログが存在しない = 異常
|
||||
$alertMessage = sprintf(
|
||||
@ -464,40 +439,7 @@ class ShjSixService
|
||||
];
|
||||
}
|
||||
|
||||
// status != 1(正常)の場合は異常
|
||||
if ($latestLog->status != HardwareCheckLog::STATUS_NORMAL) {
|
||||
$statusName = HardwareCheckLog::getStatusName($latestLog->status);
|
||||
$alertMessage = sprintf(
|
||||
'ハードウェア監視異常: デバイスID=%d, デバイス名=%s, 状態=%s, コメント=%s',
|
||||
$device->device_id,
|
||||
$device->device_subject ?? 'N/A',
|
||||
$statusName,
|
||||
$latestLog->status_comment ?? 'N/A'
|
||||
);
|
||||
|
||||
$commonAResult = $this->executeCommonProcessA(
|
||||
$batchLogId,
|
||||
$alertMessage,
|
||||
self::QUE_CLASS_SERVER_ERROR,
|
||||
$device->park_id,
|
||||
null,
|
||||
null,
|
||||
$dbRegisterFlag,
|
||||
'ハードウェア監視:異常状態検出'
|
||||
);
|
||||
|
||||
return [
|
||||
'has_alert' => true,
|
||||
'reason' => '異常状態',
|
||||
'mail_success_count' => $commonAResult['mail_success_count'] ?? 0,
|
||||
'mail_error_count' => $commonAResult['mail_error_count'] ?? 0,
|
||||
'queue_success_count' => $commonAResult['queue_success_count'] ?? 0, // 仕様書準拠
|
||||
'queue_error_count' => $commonAResult['queue_error_count'] ?? 0, // 仕様書準拠
|
||||
'updated_batch_comment' => $commonAResult['updated_batch_comment'] ?? ''
|
||||
];
|
||||
}
|
||||
|
||||
// 正常
|
||||
// 仕様書準拠:レコードが存在する場合はステータス値に関わらずJOB4へ(アラートなし)
|
||||
return [
|
||||
'has_alert' => false,
|
||||
'reason' => '正常',
|
||||
@ -562,19 +504,28 @@ class ShjSixService
|
||||
// エラーログごとに共通A処理を実行
|
||||
foreach ($errorLogs as $log) {
|
||||
// 仕様書準拠:statusの値に応じてキュー種別を判定
|
||||
// - status 200番台 → 102(プリンタエラー)
|
||||
// - status 300番台 → 103(スキャナエラー)
|
||||
// - status 400番台 → 104(プリンタ用紙残少警報)
|
||||
// - 200番台 → 102(プリンタエラー)
|
||||
// - 300番台 → 103(スキャナエラー)
|
||||
// - 400番台 → 104(プリンタ用紙残少警報)
|
||||
$statusValue = (int)$log->status;
|
||||
if ($statusValue >= 400 && $statusValue < 500) {
|
||||
$queClass = self::QUE_CLASS_PAPER_WARNING; // 104 正しい定数名
|
||||
$queClass = self::QUE_CLASS_PAPER_WARNING; // 104
|
||||
$errorType = 'プリンタ用紙残少警報';
|
||||
} elseif ($statusValue >= 300 && $statusValue < 400) {
|
||||
$queClass = self::QUE_CLASS_SCANNER_ERROR; // 103
|
||||
$errorType = 'スキャナエラー';
|
||||
} else {
|
||||
$queClass = self::QUE_CLASS_PRINTER_ERROR; // 102(デフォルト)
|
||||
} elseif ($statusValue >= 200 && $statusValue < 300) {
|
||||
$queClass = self::QUE_CLASS_PRINTER_ERROR; // 102
|
||||
$errorType = 'プリンタエラー';
|
||||
} else {
|
||||
// 仕様書未定義のステータス範囲 → 102(プリンタエラー)をデフォルト使用
|
||||
// 式様書「対象レコード数分」に準拠し、スキップしない
|
||||
$queClass = self::QUE_CLASS_PRINTER_ERROR; // 102
|
||||
$errorType = 'プリンタエラー';
|
||||
Log::warning('JOB4: 仕様書未定義のステータス範囲(デフォルト102適用)', [
|
||||
'status' => $statusValue,
|
||||
'process_name' => $log->process_name ?? null
|
||||
]);
|
||||
}
|
||||
|
||||
$alertMessage = sprintf(
|
||||
@ -618,8 +569,7 @@ class ShjSixService
|
||||
$processNames[] = $log->process_name;
|
||||
}
|
||||
}
|
||||
// 重複を除去して連結
|
||||
$processNames = array_unique($processNames);
|
||||
// 仕様書準拠:複数の場合は後ろ連結(重複除去しない)
|
||||
$concatenatedProcessNames = !empty($processNames) ? implode(',', $processNames) : null;
|
||||
|
||||
return [
|
||||
@ -676,12 +626,10 @@ class ShjSixService
|
||||
// メール送信結果統計
|
||||
$mailSuccessCount = 0;
|
||||
$mailErrorCount = 0;
|
||||
$mailErrorDetails = [];
|
||||
|
||||
// キュー登録結果統計(仕様書準拠)
|
||||
$queueSuccessCount = 0;
|
||||
$queueErrorCount = 0;
|
||||
$registeredQueId = null; // 登録したキューID(後で更新するため)
|
||||
|
||||
try {
|
||||
Log::info('共通A処理開始', [
|
||||
@ -696,12 +644,8 @@ class ShjSixService
|
||||
if ($dbRegisterFlag === 1) {
|
||||
// DB登録可能な場合
|
||||
|
||||
// 仕様書準拠:バッチコメント = 処理1.定期契約ID + 元のバッチコメント
|
||||
$updatedBatchComment = sprintf(
|
||||
'定期契約ID: %s / %s',
|
||||
$contractId ?? 'なし',
|
||||
$batchComment ?? ''
|
||||
);
|
||||
// 仕様書準拠:キューコメント = 内部変数.バッチコメント(そのまま使用)
|
||||
$updatedBatchComment = $batchComment ?? '';
|
||||
|
||||
// 【共通処理1】オペレータキューを登録する(仕様書順序準拠)
|
||||
// 注:SHJ-7異常情報はメール送信後に追記
|
||||
@ -718,13 +662,11 @@ class ShjSixService
|
||||
// 仕様書準拠:キュー登録正常終了件数/異常終了件数を更新
|
||||
if ($queueResult['success']) {
|
||||
$queueSuccessCount++;
|
||||
$registeredQueId = $queueResult['que_id']; // キューIDを保存
|
||||
} else {
|
||||
$queueErrorCount++;
|
||||
$updatedBatchComment .= sprintf(
|
||||
' | キュー登録異常: %s',
|
||||
$queueResult['error'] ?? 'Unknown error'
|
||||
);
|
||||
// 仕様書準拠(line 209):定期契約ID + 異常情報をバッチコメントに追記
|
||||
$errorPrefix = ($contractId !== null) ? $contractId : '';
|
||||
$updatedBatchComment .= $errorPrefix . ($queueResult['error'] ?? '');
|
||||
}
|
||||
|
||||
// 【共通処理2】メール送信対象オペレータを取得する
|
||||
@ -740,11 +682,9 @@ class ShjSixService
|
||||
$mailSuccessCount++;
|
||||
} else {
|
||||
$mailErrorCount++;
|
||||
$mailErrorDetails[] = sprintf(
|
||||
'オペレータ[%s]へのメール送信失敗: %s',
|
||||
$operator['email'],
|
||||
$result['error'] ?? 'Unknown error'
|
||||
);
|
||||
// 仕様書準拠(line 209):定期契約ID + SHJ-7.異常情報をバッチコメントに追記
|
||||
$errorPrefix = ($contractId !== null) ? $contractId : '';
|
||||
$updatedBatchComment .= $errorPrefix . ($result['error'] ?? '');
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -762,45 +702,13 @@ class ShjSixService
|
||||
$mailSuccessCount++;
|
||||
} else {
|
||||
$mailErrorCount++;
|
||||
$mailErrorDetails[] = sprintf(
|
||||
'駐輪場管理者[%s]へのメール送信失敗: %s',
|
||||
$manager['email'],
|
||||
$result['error'] ?? 'Unknown error'
|
||||
);
|
||||
// 仕様書準拠(line 209):定期契約ID + SHJ-7.異常情報をバッチコメントに追記
|
||||
$errorPrefix = ($contractId !== null) ? $contractId : '';
|
||||
$updatedBatchComment .= $errorPrefix . ($result['error'] ?? '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 仕様書準拠:メール異常時にSHJ-7異常情報を追記
|
||||
if ($mailErrorCount > 0) {
|
||||
$updatedBatchComment .= sprintf(
|
||||
' | SHJ-7メール異常: %d件 (%s)',
|
||||
$mailErrorCount,
|
||||
implode('; ', $mailErrorDetails)
|
||||
);
|
||||
}
|
||||
|
||||
// 仕様書準拠:キュー登録後、SHJ-7異常情報を含めてキューコメントを更新
|
||||
// キューコメント = 内部変数.バッチコメント(定期契約ID + SHJ-7異常情報)
|
||||
if ($registeredQueId && $mailErrorCount > 0) {
|
||||
try {
|
||||
OperatorQue::where('que_id', $registeredQueId)->update([ // 主キーはque_id
|
||||
'que_comment' => $updatedBatchComment, // 仕様書:キューコメントに追記
|
||||
'updated_at' => now()
|
||||
]);
|
||||
|
||||
Log::info('オペレータキューコメント更新完了(SHJ-7異常情報追記)', [
|
||||
'que_id' => $registeredQueId,
|
||||
'updated_comment' => $updatedBatchComment
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('オペレータキューコメント更新エラー', [
|
||||
'que_id' => $registeredQueId,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// DB反映NGの場合は固定メールアドレスに緊急メール送信
|
||||
// 仕様書準拠:テンプレート不使用、件名固定、本文なし
|
||||
@ -809,24 +717,13 @@ class ShjSixService
|
||||
$alertMessage
|
||||
);
|
||||
|
||||
$updatedBatchComment = $batchComment ?? '';
|
||||
if ($result['success']) {
|
||||
$mailSuccessCount++;
|
||||
} else {
|
||||
$mailErrorCount++;
|
||||
$mailErrorDetails[] = sprintf(
|
||||
'固定アドレス[%s]への緊急メール送信失敗: %s',
|
||||
self::FIXED_EMAIL_ADDRESS,
|
||||
$result['error'] ?? 'Unknown error'
|
||||
);
|
||||
}
|
||||
|
||||
$updatedBatchComment = $batchComment ?? '';
|
||||
if ($mailErrorCount > 0) {
|
||||
$updatedBatchComment .= sprintf(
|
||||
' | 緊急メール異常: %d件 (%s)',
|
||||
$mailErrorCount,
|
||||
implode('; ', $mailErrorDetails)
|
||||
);
|
||||
// 仕様書準拠:異常情報をバッチコメントに直接追記
|
||||
$updatedBatchComment .= $result['error'] ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
@ -844,7 +741,6 @@ class ShjSixService
|
||||
'success' => true,
|
||||
'mail_success_count' => $mailSuccessCount,
|
||||
'mail_error_count' => $mailErrorCount,
|
||||
'mail_error_details' => $mailErrorDetails,
|
||||
'queue_success_count' => $queueSuccessCount, // 仕様書準拠
|
||||
'queue_error_count' => $queueErrorCount, // 仕様書準拠
|
||||
'updated_batch_comment' => $updatedBatchComment ?? $batchComment
|
||||
@ -863,7 +759,6 @@ class ShjSixService
|
||||
'success' => false,
|
||||
'mail_success_count' => $mailSuccessCount,
|
||||
'mail_error_count' => $mailErrorCount,
|
||||
'mail_error_details' => $mailErrorDetails,
|
||||
'queue_success_count' => $queueSuccessCount,
|
||||
'queue_error_count' => $queueErrorCount,
|
||||
'updated_batch_comment' => $updatedBatchComment,
|
||||
@ -876,8 +771,9 @@ class ShjSixService
|
||||
* オペレータキューを登録
|
||||
*
|
||||
* 仕様書準拠:
|
||||
* - 定期契約IDをバッチコメントに含める
|
||||
* - 登録の成否とque_idを返却(後で更新するため)
|
||||
* - キューコメント = 内部変数.バッチコメント
|
||||
* - キューステータスID = 1(キュー発生)
|
||||
* - 更新オペレータID = 9999999(機器ID固定値)
|
||||
*
|
||||
* @param string $alertMessage アラートメッセージ
|
||||
* @param int|null $batchLogId バッチログID
|
||||
@ -885,7 +781,7 @@ class ShjSixService
|
||||
* @param int|null $parkId 駐輪場ID
|
||||
* @param int|null $userId 利用者ID
|
||||
* @param int|null $contractId 定期契約ID
|
||||
* @param string|null $batchComment バッチコメント(SHJ-7異常情報は後で追記)
|
||||
* @param string|null $batchComment バッチコメント
|
||||
* @return array 登録結果 ['success' => bool, 'error' => string|null, 'que_id' => int|null]
|
||||
*/
|
||||
private function registerOperatorQueue(
|
||||
@ -959,12 +855,20 @@ class ShjSixService
|
||||
* - 104(プリンタ用紙残少) → ope_sendalart_que13
|
||||
*
|
||||
* @param int $queClass 対象キュー種別ID
|
||||
* @param int|null $parkId 駐輪場ID(nullの場合は全オペレータ)
|
||||
* @param int|null $parkId 駐輪場ID
|
||||
* @return array オペレータ一覧
|
||||
*/
|
||||
private function getMailTargetOperators(int $queClass, ?int $parkId): array
|
||||
{
|
||||
try {
|
||||
// 仕様書準拠:駐輪場IDがnullの場合はJOIN条件が成立しないため空返却
|
||||
if ($parkId === null) {
|
||||
Log::info('park_idがnullのため、メール送信対象オペレータなし', [
|
||||
'que_class' => $queClass
|
||||
]);
|
||||
return [];
|
||||
}
|
||||
|
||||
// キュー種別IDに対応する送信フラグカラム名を決定
|
||||
$alertFlagColumn = $this->getOperatorAlertFlagColumn($queClass);
|
||||
|
||||
@ -973,20 +877,17 @@ class ShjSixService
|
||||
return [];
|
||||
}
|
||||
|
||||
// 管轄駐輪場マスタをJOINしてオペレータを取得
|
||||
// 仕様書準拠SQL-6:管轄駐輪場マスタをJOINしてオペレータを取得
|
||||
$query = DB::table('ope as T1')
|
||||
->select(['T1.ope_id', 'T1.ope_name', 'T1.ope_mail'])
|
||||
->join('jurisdiction_parking as T2', 'T1.ope_id', '=', 'T2.ope_id')
|
||||
// 仕様書SQL-6: ope_login_id と記載があるが、実DBカラム名は login_id
|
||||
->select(['T1.ope_id', 'T1.login_id', 'T1.ope_name', 'T1.ope_mail'])
|
||||
->where('T2.park_id', $parkId)
|
||||
->where('T1.' . $alertFlagColumn, 1) // 該当アラート送信フラグが有効
|
||||
->where('T1.ope_quit_flag', 0) // 退職していない
|
||||
->whereNotNull('T1.ope_mail')
|
||||
->where('T1.ope_mail', '!=', '');
|
||||
|
||||
// 駐輪場IDが指定されている場合は管轄駐輪場マスタでフィルタ
|
||||
if ($parkId !== null) {
|
||||
$query->join('jurisdiction_parking as T2', 'T1.ope_id', '=', 'T2.ope_id')
|
||||
->where('T2.park_id', $parkId);
|
||||
}
|
||||
|
||||
$operators = $query->get()
|
||||
->map(function ($ope) {
|
||||
return [
|
||||
@ -1043,19 +944,21 @@ class ShjSixService
|
||||
* - メールアドレスが設定されている
|
||||
* - 所属駐輪場ID = 指定駐輪場ID
|
||||
*
|
||||
* @param int|null $parkId 駐輪場ID(nullの場合は全管理者)
|
||||
* @param int|null $parkId 駐輪場ID
|
||||
* @return array 駐輪場管理者一覧
|
||||
*/
|
||||
private function getParkManagers(?int $parkId): array
|
||||
{
|
||||
try {
|
||||
$query = Manager::active()->hasEmail();
|
||||
|
||||
// 駐輪場IDが指定されている場合はフィルタ
|
||||
if ($parkId !== null) {
|
||||
$query->where('manager_parkid', $parkId);
|
||||
// 仕様書準拠:駐輪場IDがnullの場合は該当なしのため空返却
|
||||
if ($parkId === null) {
|
||||
Log::info('park_idがnullのため、駐輪場管理者なし');
|
||||
return [];
|
||||
}
|
||||
|
||||
$query = Manager::active()->hasEmail()
|
||||
->where('manager_parkid', $parkId);
|
||||
|
||||
$managers = $query->select(['manager_id', 'manager_name', 'manager_mail', 'manager_parkid'])
|
||||
->get()
|
||||
->map(function ($manager) {
|
||||
@ -1205,32 +1108,26 @@ class ShjSixService
|
||||
*
|
||||
* 仕様書に基づくSHJ-8共通処理呼び出し
|
||||
*
|
||||
* @param array $statistics 処理統計情報
|
||||
* @param int $deviceId デバイスID
|
||||
* @param string|null $processName プロセス名(JOB4から取得)
|
||||
* @param string $jobName ジョブ名
|
||||
* @param string $status ステータス
|
||||
* @param string $statusComment ステータスコメント
|
||||
* @return array 処理結果 ['success' => bool, 'error' => string|null]
|
||||
*/
|
||||
private function createShjBatchLog(array $statistics): array
|
||||
{
|
||||
private function createShjBatchLog(
|
||||
int $deviceId,
|
||||
?string $processName,
|
||||
string $jobName,
|
||||
string $status,
|
||||
string $statusComment
|
||||
): array {
|
||||
try {
|
||||
// 仕様書準拠のSHJ-8パラメータ設定
|
||||
// device_id: 処理2で取得したデバイスID(複数ある場合は最初のIDを使用)
|
||||
// process_name: 処理4のプロセス名
|
||||
$deviceIdString = $statistics['device_id']; // 連結されたデバイスID文字列 "1,2,3"
|
||||
|
||||
// 複数デバイスIDがある場合は最初のIDを使用(SHJ-8はint型を期待)
|
||||
$deviceIdArray = !empty($deviceIdString) ? explode(',', $deviceIdString) : [];
|
||||
$deviceId = !empty($deviceIdArray) ? (int)$deviceIdArray[0] : 1;
|
||||
|
||||
$processName = $statistics['process_name'] ?? 'SHJ-6';
|
||||
$jobName = $statistics['job_name']; // "SHJ-6サーバ死活監視" 固定
|
||||
$status = $statistics['status']; // 常に "success"
|
||||
$statusComment = $statistics['status_comment'] ?? '';
|
||||
|
||||
$createdDate = now()->format('Y/m/d');
|
||||
$updatedDate = now()->format('Y/m/d');
|
||||
|
||||
Log::info('SHJ-8 バッチ処理ログ作成', [
|
||||
'device_id' => $deviceId,
|
||||
'device_id_original' => $deviceIdString,
|
||||
'process_name' => $processName,
|
||||
'job_name' => $jobName,
|
||||
'status' => $status,
|
||||
@ -1238,7 +1135,7 @@ class ShjSixService
|
||||
]);
|
||||
|
||||
// SHJ-8サービスを呼び出し
|
||||
$this->shjEightService->execute(
|
||||
$shj8Result = $this->shjEightService->execute(
|
||||
$deviceId,
|
||||
$processName,
|
||||
$jobName,
|
||||
@ -1248,20 +1145,33 @@ class ShjSixService
|
||||
$updatedDate
|
||||
);
|
||||
|
||||
Log::info('SHJ-8 バッチ処理ログ作成完了', [
|
||||
'device_id' => $deviceId,
|
||||
'process_name' => $processName
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'error' => null
|
||||
];
|
||||
// 仕様書準拠:SHJ-8の処理結果を判定(result=0が正常)
|
||||
if (($shj8Result['result'] ?? 1) === 0) {
|
||||
Log::info('SHJ-8 バッチ処理ログ作成完了(正常)', [
|
||||
'device_id' => $deviceId,
|
||||
'process_name' => $processName
|
||||
]);
|
||||
return [
|
||||
'success' => true,
|
||||
'error' => null
|
||||
];
|
||||
} else {
|
||||
$errorMessage = $shj8Result['error_message'] ?? 'SHJ-8 処理結果異常';
|
||||
Log::warning('SHJ-8 バッチ処理ログ作成異常', [
|
||||
'device_id' => $deviceId,
|
||||
'result' => $shj8Result['result'] ?? null,
|
||||
'error_message' => $errorMessage
|
||||
]);
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => $errorMessage
|
||||
];
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('SHJ-8 バッチ処理ログ作成エラー', [
|
||||
'error' => $e->getMessage(),
|
||||
'statistics' => $statistics
|
||||
'device_id' => $deviceId
|
||||
]);
|
||||
|
||||
// 仕様書準拠:SHJ-8でエラーが発生してもメイン処理は継続
|
||||
|
||||
470
app/Services/ShjTwoService.php
Normal file
470
app/Services/ShjTwoService.php
Normal file
@ -0,0 +1,470 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Device;
|
||||
use App\Services\ShjEightService;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Carbon\Carbon;
|
||||
|
||||
/**
|
||||
* SHJ-2 データバックアップサービス
|
||||
*
|
||||
* 概要: データベースの夜間自動フルバックアップ。5世代保持。
|
||||
*
|
||||
* 処理フロー:
|
||||
* <JOB1> 現時点の世代シフトを行う
|
||||
* <JOB2> フルバックアップを行う(ソース + DB)
|
||||
* <JOB3> バッチ処理ログを作成する(SHJ-8呼び出し)
|
||||
*/
|
||||
class ShjTwoService
|
||||
{
|
||||
/**
|
||||
* ShjEightService インスタンス
|
||||
*
|
||||
* @var ShjEightService
|
||||
*/
|
||||
protected $shjEightService;
|
||||
|
||||
/**
|
||||
* コンストラクタ
|
||||
*
|
||||
* @param ShjEightService $shjEightService
|
||||
*/
|
||||
public function __construct(ShjEightService $shjEightService)
|
||||
{
|
||||
$this->shjEightService = $shjEightService;
|
||||
}
|
||||
|
||||
/**
|
||||
* SHJ-2 メイン処理実行
|
||||
*
|
||||
* @return array 処理結果
|
||||
*/
|
||||
public function execute(): array
|
||||
{
|
||||
$status = 'error';
|
||||
$statusComment = '';
|
||||
|
||||
try {
|
||||
Log::info('SHJ-2 データバックアップ処理開始');
|
||||
|
||||
$backupRoot = config('shj2.backup_root');
|
||||
$dbName = config('database.connections.mysql.database');
|
||||
$generations = config('shj2.generations', 5);
|
||||
|
||||
// <JOB1> 世代シフト
|
||||
$this->shiftGenerations($backupRoot, $dbName, $generations);
|
||||
|
||||
// <JOB2> フルバックアップ
|
||||
$backupResult = $this->executeFullBackup($backupRoot, $dbName);
|
||||
|
||||
// [JOB2-STEP1] 結果設定
|
||||
$status = $backupResult['status'];
|
||||
$statusComment = $backupResult['status_comment'];
|
||||
|
||||
Log::info('SHJ-2 データバックアップ処理完了', [
|
||||
'status' => $status,
|
||||
'status_comment' => $statusComment
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$status = 'error';
|
||||
$statusComment = $e->getMessage();
|
||||
|
||||
Log::error('SHJ-2 データバックアップ処理エラー', [
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
}
|
||||
|
||||
// <JOB3> バッチ処理ログ作成(成功・失敗に関わらず実行)
|
||||
$this->createBatchLog($status, $statusComment);
|
||||
|
||||
return [
|
||||
'success' => $status === 'success',
|
||||
'status' => $status,
|
||||
'status_comment' => $statusComment
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* <JOB1> 世代シフト処理
|
||||
*
|
||||
* ・5世代前 削除
|
||||
* ・4世代前 5世代前へ移動
|
||||
* ・3世代前 4世代前へ移動
|
||||
* ・2世代前 3世代前へ移動
|
||||
* ・1世代前(<DB名>直下)→ 2世代前へ移動
|
||||
*
|
||||
* @param string $backupRoot バックアップルートディレクトリ
|
||||
* @param string $dbName データベース名
|
||||
* @param int $generations 世代数
|
||||
* @return void
|
||||
*/
|
||||
private function shiftGenerations(string $backupRoot, string $dbName, int $generations): void
|
||||
{
|
||||
Log::info('JOB1 世代シフト開始', [
|
||||
'backup_root' => $backupRoot,
|
||||
'db_name' => $dbName,
|
||||
'generations' => $generations
|
||||
]);
|
||||
|
||||
// 最古世代(gen5)のディレクトリパスを取得して削除
|
||||
$oldestDir = $backupRoot . '/' . $dbName . '_' . $generations;
|
||||
if (is_dir($oldestDir)) {
|
||||
$this->deleteDirectory($oldestDir);
|
||||
Log::info("世代シフト: {$generations}世代前を削除", ['path' => $oldestDir]);
|
||||
}
|
||||
|
||||
// gen4→gen5, gen3→gen4, gen2→gen3 のシフト
|
||||
for ($i = $generations - 1; $i >= 2; $i--) {
|
||||
$fromDir = $backupRoot . '/' . $dbName . '_' . $i;
|
||||
$toDir = $backupRoot . '/' . $dbName . '_' . ($i + 1);
|
||||
|
||||
if (is_dir($fromDir)) {
|
||||
rename($fromDir, $toDir);
|
||||
Log::info("世代シフト: {$i}世代前 → " . ($i + 1) . "世代前", [
|
||||
'from' => $fromDir,
|
||||
'to' => $toDir
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// gen1(<DB名>直下)→ gen2 へ移動
|
||||
$gen1Dir = $backupRoot . '/' . $dbName;
|
||||
$gen2Dir = $backupRoot . '/' . $dbName . '_2';
|
||||
|
||||
if (is_dir($gen1Dir)) {
|
||||
rename($gen1Dir, $gen2Dir);
|
||||
Log::info('世代シフト: 1世代前 → 2世代前', [
|
||||
'from' => $gen1Dir,
|
||||
'to' => $gen2Dir
|
||||
]);
|
||||
}
|
||||
|
||||
Log::info('JOB1 世代シフト完了');
|
||||
}
|
||||
|
||||
/**
|
||||
* <JOB2> フルバックアップ実行
|
||||
*
|
||||
* 保存内容: {vendor以外のソース + DB}_YYYYMMDD.tar.gz
|
||||
*
|
||||
* @param string $backupRoot バックアップルートディレクトリ
|
||||
* @param string $dbName データベース名
|
||||
* @return array ['status' => string, 'status_comment' => string]
|
||||
*/
|
||||
private function executeFullBackup(string $backupRoot, string $dbName): array
|
||||
{
|
||||
Log::info('JOB2 フルバックアップ開始');
|
||||
|
||||
$today = Carbon::now()->format('Ymd');
|
||||
$backupDir = $backupRoot . '/' . $dbName;
|
||||
$tarFileName = $dbName . '_' . $today . '.tar';
|
||||
$gzFileName = $tarFileName . '.gz';
|
||||
$tarFilePath = $backupDir . '/' . $tarFileName;
|
||||
$gzFilePath = $backupDir . '/' . $gzFileName;
|
||||
|
||||
// バックアップディレクトリ作成
|
||||
if (!is_dir($backupDir)) {
|
||||
mkdir($backupDir, 0755, true);
|
||||
}
|
||||
|
||||
// 一時ディレクトリ(tar作成用のステージング領域)
|
||||
$tempDir = $backupDir . '/temp_' . $today;
|
||||
if (!is_dir($tempDir)) {
|
||||
mkdir($tempDir, 0755, true);
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. mysqldump実行
|
||||
$sqlFileName = $dbName . '.sql';
|
||||
$sqlFilePath = $tempDir . '/' . $sqlFileName;
|
||||
$this->executeMysqlDump($sqlFilePath);
|
||||
|
||||
// 2. ソースファイルをコピー
|
||||
$this->copySourceFiles($tempDir);
|
||||
|
||||
// 3. tar.gz作成
|
||||
$this->createTarGz($tempDir, $gzFilePath);
|
||||
|
||||
// 4. ファイルサイズ検証
|
||||
if (!file_exists($gzFilePath) || filesize($gzFilePath) === 0) {
|
||||
throw new \RuntimeException('バックアップファイルの作成に失敗しました(ファイルサイズ0)');
|
||||
}
|
||||
|
||||
$fileSizeMb = round(filesize($gzFilePath) / 1024 / 1024, 2);
|
||||
|
||||
Log::info('JOB2 フルバックアップ完了', [
|
||||
'file' => $gzFilePath,
|
||||
'size_mb' => $fileSizeMb
|
||||
]);
|
||||
|
||||
// [JOB2-STEP1] OK
|
||||
return [
|
||||
'status' => 'success',
|
||||
'status_comment' => $backupDir . ' ' . $gzFileName
|
||||
];
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('JOB2 フルバックアップエラー', [
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
|
||||
// [JOB2-STEP1] NG
|
||||
return [
|
||||
'status' => 'error',
|
||||
'status_comment' => $e->getMessage()
|
||||
];
|
||||
|
||||
} finally {
|
||||
// 一時ディレクトリを削除
|
||||
if (is_dir($tempDir)) {
|
||||
$this->deleteDirectory($tempDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* mysqldump実行
|
||||
*
|
||||
* @param string $outputPath 出力ファイルパス
|
||||
* @return void
|
||||
* @throws \RuntimeException dump失敗時
|
||||
*/
|
||||
private function executeMysqlDump(string $outputPath): void
|
||||
{
|
||||
$mysqldumpPath = config('shj2.mysqldump_path');
|
||||
$dbHost = config('database.connections.mysql.host');
|
||||
$dbPort = config('database.connections.mysql.port', '3306');
|
||||
$dbName = config('database.connections.mysql.database');
|
||||
$dbUser = config('database.connections.mysql.username');
|
||||
$dbPass = config('database.connections.mysql.password');
|
||||
|
||||
// mysqldumpコマンド構築(Windows環境対応)
|
||||
// stderrは一時ファイルに出力し、エラー時に読み取る
|
||||
$stderrFile = sys_get_temp_dir() . '/shj2_mysqldump_stderr.txt';
|
||||
$command = sprintf(
|
||||
'"%s" --host="%s" --port="%s" --user="%s" --password="%s" --single-transaction --routines --triggers "%s" > "%s" 2>"%s"',
|
||||
$mysqldumpPath,
|
||||
$dbHost,
|
||||
$dbPort,
|
||||
$dbUser,
|
||||
$dbPass,
|
||||
$dbName,
|
||||
$outputPath,
|
||||
$stderrFile
|
||||
);
|
||||
|
||||
Log::info('mysqldump実行開始', ['database' => $dbName]);
|
||||
|
||||
exec($command, $output, $returnCode);
|
||||
|
||||
if ($returnCode !== 0) {
|
||||
$errorOutput = file_exists($stderrFile) ? trim(file_get_contents($stderrFile)) : '不明なエラー';
|
||||
@unlink($stderrFile);
|
||||
throw new \RuntimeException('mysqldumpエラー: ' . $errorOutput);
|
||||
}
|
||||
@unlink($stderrFile);
|
||||
|
||||
if (!file_exists($outputPath) || filesize($outputPath) === 0) {
|
||||
throw new \RuntimeException('mysqldumpの出力ファイルが空です');
|
||||
}
|
||||
|
||||
Log::info('mysqldump実行完了', [
|
||||
'output_path' => $outputPath,
|
||||
'size_bytes' => filesize($outputPath)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* ソースファイルを一時ディレクトリにコピー
|
||||
*
|
||||
* vendor等の除外ディレクトリを除いてコピーする
|
||||
*
|
||||
* @param string $tempDir コピー先一時ディレクトリ
|
||||
* @return void
|
||||
*/
|
||||
private function copySourceFiles(string $tempDir): void
|
||||
{
|
||||
$sourcePaths = config('shj2.source_paths', []);
|
||||
$excludeDirs = config('shj2.exclude_dirs', []);
|
||||
|
||||
foreach ($sourcePaths as $sourcePath) {
|
||||
// 空パスはスキップ
|
||||
if (empty($sourcePath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!is_dir($sourcePath)) {
|
||||
Log::warning('ソースディレクトリが存在しません', ['path' => $sourcePath]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// ディレクトリ名を取得してコピー先を決定
|
||||
$dirName = basename($sourcePath);
|
||||
$destDir = $tempDir . '/' . $dirName;
|
||||
|
||||
Log::info('ソースコピー開始', [
|
||||
'source' => $sourcePath,
|
||||
'dest' => $destDir
|
||||
]);
|
||||
|
||||
$this->copyDirectoryRecursive($sourcePath, $destDir, $excludeDirs);
|
||||
|
||||
Log::info('ソースコピー完了', ['dir_name' => $dirName]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ディレクトリを再帰的にコピー(除外ディレクトリ対応)
|
||||
*
|
||||
* @param string $source コピー元パス
|
||||
* @param string $dest コピー先パス
|
||||
* @param array $excludeDirs 除外ディレクトリ名リスト
|
||||
* @return void
|
||||
*/
|
||||
private function copyDirectoryRecursive(string $source, string $dest, array $excludeDirs): void
|
||||
{
|
||||
if (!is_dir($dest)) {
|
||||
mkdir($dest, 0755, true);
|
||||
}
|
||||
|
||||
$iterator = new \DirectoryIterator($source);
|
||||
|
||||
foreach ($iterator as $item) {
|
||||
if ($item->isDot()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$itemName = $item->getFilename();
|
||||
|
||||
// 除外ディレクトリチェック
|
||||
if ($item->isDir() && in_array($itemName, $excludeDirs)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$sourcePath = $item->getPathname();
|
||||
$destPath = $dest . '/' . $itemName;
|
||||
|
||||
if ($item->isDir()) {
|
||||
$this->copyDirectoryRecursive($sourcePath, $destPath, $excludeDirs);
|
||||
} else {
|
||||
copy($sourcePath, $destPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* tar.gzアーカイブを作成
|
||||
*
|
||||
* @param string $sourceDir アーカイブ対象ディレクトリ
|
||||
* @param string $gzFilePath 出力tar.gzファイルパス
|
||||
* @return void
|
||||
* @throws \RuntimeException 作成失敗時
|
||||
*/
|
||||
private function createTarGz(string $sourceDir, string $gzFilePath): void
|
||||
{
|
||||
Log::info('tar.gz作成開始', ['source' => $sourceDir, 'output' => $gzFilePath]);
|
||||
|
||||
// tarファイルパス(.gzを除いたパス)
|
||||
$tarFilePath = preg_replace('/\.gz$/', '', $gzFilePath);
|
||||
|
||||
try {
|
||||
$phar = new \PharData($tarFilePath);
|
||||
$phar->buildFromDirectory($sourceDir);
|
||||
$phar->compress(\Phar::GZ);
|
||||
|
||||
// PharData::compress() は新しい .tar.gz ファイルを作成する
|
||||
// 元の .tar ファイルを削除
|
||||
if (file_exists($tarFilePath)) {
|
||||
unlink($tarFilePath);
|
||||
}
|
||||
|
||||
Log::info('tar.gz作成完了', ['output' => $gzFilePath]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
// 中間ファイルのクリーンアップ
|
||||
if (file_exists($tarFilePath)) {
|
||||
unlink($tarFilePath);
|
||||
}
|
||||
if (file_exists($gzFilePath)) {
|
||||
unlink($gzFilePath);
|
||||
}
|
||||
throw new \RuntimeException('tar.gz作成エラー: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <JOB3> バッチ処理ログ作成
|
||||
*
|
||||
* SHJ-8 バッチ処理ログ作成を呼び出す
|
||||
*
|
||||
* @param string $status ステータス(success/error)
|
||||
* @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');
|
||||
|
||||
// ステータスコメントは255文字以内に切り詰め
|
||||
if (mb_strlen($statusComment) > 255) {
|
||||
$statusComment = mb_substr($statusComment, 0, 252) . '...';
|
||||
}
|
||||
|
||||
$this->shjEightService->execute(
|
||||
$deviceId,
|
||||
null, // プロセス名
|
||||
'SHJ-2データバックアップ', // ジョブ名
|
||||
$status, // ステータス
|
||||
$statusComment, // ステータスコメント
|
||||
$today, // 登録日時
|
||||
$today // 更新日時
|
||||
);
|
||||
|
||||
Log::info('JOB3 バッチ処理ログ作成完了', [
|
||||
'device_id' => $deviceId,
|
||||
'status' => $status
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('JOB3 バッチ処理ログ作成エラー', [
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ディレクトリを再帰的に削除
|
||||
*
|
||||
* @param string $dirPath 削除対象ディレクトリパス
|
||||
* @return void
|
||||
*/
|
||||
private function deleteDirectory(string $dirPath): void
|
||||
{
|
||||
if (!is_dir($dirPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$items = new \RecursiveIteratorIterator(
|
||||
new \RecursiveDirectoryIterator($dirPath, \RecursiveDirectoryIterator::SKIP_DOTS),
|
||||
\RecursiveIteratorIterator::CHILD_FIRST
|
||||
);
|
||||
|
||||
foreach ($items as $item) {
|
||||
if ($item->isDir()) {
|
||||
rmdir($item->getPathname());
|
||||
} else {
|
||||
unlink($item->getPathname());
|
||||
}
|
||||
}
|
||||
|
||||
rmdir($dirPath);
|
||||
}
|
||||
}
|
||||
61
config/shj2.php
Normal file
61
config/shj2.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SHJ-2 データバックアップ設定
|
||||
*
|
||||
* バックアップ対象パス、保存先、世代数などの設定
|
||||
* 環境に応じて .env ファイルで上書き可能
|
||||
*/
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| バックアップ保存先ルートディレクトリ
|
||||
|--------------------------------------------------------------------------
|
||||
| 世代ディレクトリの親ディレクトリ
|
||||
| 例: C:/xampp8/backup/somanager/krgm/ が1世代前のバックアップ先
|
||||
*/
|
||||
'backup_root' => env('SHJ2_BACKUP_ROOT', 'C:/xampp8/backup/somanager'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| バックアップ対象ソースディレクトリ
|
||||
|--------------------------------------------------------------------------
|
||||
| vendor等を除外してバックアップするプロジェクトパス
|
||||
| 空文字列のパスは自動的にスキップされる
|
||||
*/
|
||||
'source_paths' => [
|
||||
env('SHJ2_SOURCE_1', 'C:/xampp8/htdocs/somanager/api-batch.app'),
|
||||
env('SHJ2_SOURCE_2', 'C:/xampp8/htdocs/somanager/www.app'),
|
||||
env('SHJ2_SOURCE_3', ''), // 第3プロジェクト(パス設定後に有効)
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| 除外ディレクトリ
|
||||
|--------------------------------------------------------------------------
|
||||
| バックアップ対象から除外するディレクトリ名
|
||||
*/
|
||||
'exclude_dirs' => [
|
||||
'vendor',
|
||||
'node_modules',
|
||||
'.git',
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| 世代数
|
||||
|--------------------------------------------------------------------------
|
||||
| フルバックアップを保持する世代数
|
||||
*/
|
||||
'generations' => 5,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| mysqldumpパス
|
||||
|--------------------------------------------------------------------------
|
||||
| mysqldumpコマンドの絶対パス
|
||||
*/
|
||||
'mysqldump_path' => env('SHJ2_MYSQLDUMP_PATH', 'C:/xampp8/mysql/bin/mysqldump'),
|
||||
|
||||
];
|
||||
@ -11,5 +11,14 @@ Artisan::command('inspire', function () {
|
||||
// 支払期限切れチェック(15分毎)
|
||||
Schedule::command('payment:expire')->everyFifteenMinutes();
|
||||
|
||||
// SHJ-2 データバックアップ(毎日 02:45)
|
||||
Schedule::command('shj:2')->dailyAt('02:45');
|
||||
|
||||
// SHJ-5 駐輪場空きチェック(毎月20日 11:00)
|
||||
Schedule::command('shj:5')->monthlyOn(20, '11:00');
|
||||
|
||||
// SHJ-6 サーバ死活監視(15分毎)
|
||||
Schedule::command('shj:6')->everyFifteenMinutes();
|
||||
|
||||
// SHJ-9 売上集計(日次)(毎日 02:00)
|
||||
Schedule::command('shj:9')->dailyAt('02:00');
|
||||
|
||||
Loading…
Reference in New Issue
Block a user