Compare commits

...

16 Commits

Author SHA1 Message Date
20d99f2102 Merge pull request 'main_go' (#8) from main_go into main
All checks were successful
Deploy main / deploy (push) Successful in 22s
Reviewed-on: #8
2025-08-22 22:29:26 +09:00
1826b85e30 Merge branch 'main' into main_go
All checks were successful
Deploy preview (main_go) / deploy (push) Successful in 11s
2025-08-22 22:29:10 +09:00
Your Name
cdbf29bb47 fix index
All checks were successful
Deploy preview (main_go) / deploy (push) Successful in 12s
2025-08-22 22:27:55 +09:00
Your Name
71986a2df1 feat: 实装SHJ-6/9/10バッチ処理システム
All checks were successful
Deploy preview (main_go) / deploy (push) Successful in 14s
- SHJ-9: 日次売上集計処理
- SHJ-10: 年次月次売上集計処理
- SHJ-6: サーバ死活監視処理
- 各種モデルサービスコマンド追加
- earnings_summary, device, hardware_check_log, print_job_log テーブル用SQL追加
2025-08-22 19:44:06 +09:00
6711341c4e 更新 public/index.php
All checks were successful
Deploy preview (main_go) / deploy (push) Successful in 12s
2025-08-22 02:36:33 +09:00
91877e332b 更新 .env
All checks were successful
Deploy preview (main_go) / deploy (push) Successful in 11s
2025-08-22 02:05:02 +09:00
89fa4e999a 更新 .env
Some checks failed
Deploy preview (main_go) / deploy (push) Failing after 2s
2025-08-22 02:04:53 +09:00
7380460938 更新 .env
Some checks failed
Deploy preview (main_go) / deploy (push) Failing after 2s
2025-08-22 02:04:39 +09:00
bdf98d4916 更新 .env
All checks were successful
Deploy preview (main_go) / deploy (push) Successful in 21s
2025-08-21 23:59:03 +09:00
dc2e0656ae 更新 .env
All checks were successful
Deploy preview (main_go) / deploy (push) Successful in 21s
2025-08-21 23:45:57 +09:00
17aa80c8eb 更新 .gitea/workflows/deploy-preview.yml
All checks were successful
Deploy preview (main_go) / deploy (push) Successful in 21s
2025-08-21 23:23:48 +09:00
7117ca0d67 更新 .env
All checks were successful
Deploy preview (main_go) / deploy (push) Successful in 11s
2025-08-21 22:42:33 +09:00
3287f7c01f 更新 .gitea/workflows/deploy-preview.yml
All checks were successful
Deploy preview (main_go) / deploy (push) Successful in 10s
2025-08-21 22:41:37 +09:00
3edbbff338 更新 .gitea/workflows/deploy-preview.yml
All checks were successful
Deploy previews (main_*) / preview (push) Successful in 11s
2025-08-21 16:33:36 +09:00
cd4bef3633 更新 test.html 2025-08-21 16:32:20 +09:00
afe82b8f2d .env を更新 2025-08-21 16:11:22 +09:00
36 changed files with 3894 additions and 0 deletions

View File

@ -0,0 +1,20 @@
name: Deploy preview (main_go)
on:
push:
branches: ["main_go"]
workflow_dispatch:
concurrency:
group: deploy-main_go
cancel-in-progress: true
jobs:
deploy:
runs-on: ["native"]
steps:
- uses: actions/checkout@v4
- name: Deploy to preview (main_go)
env:
BRANCH: main_go
run: /usr/local/bin/deploy_branch_simple.sh

1
.gitignore vendored
View File

@ -21,3 +21,4 @@ yarn-error.log
/.nova
/.vscode
/.zed
/docs

View File

@ -0,0 +1,206 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
use App\Services\ShjNineService;
/**
* SHJ-9 売上集計処理コマンド
*
* 駐輪場の売上データを日次・月次・年次で集計する処理を実行する
* バックグラウンドで実行される定期バッチ処理
*/
class ShjNineCommand extends Command
{
/**
* コンソールコマンドの名前とシグネチャ
*
* 引数:
* - type: 集計種別 (daily/monthly/yearly) (必須)
* - target_date: 集計対象日 (オプション、YYYY-MM-DD形式)
*
* @var string
*/
protected $signature = 'shj:9 {type : 集計種別(daily/monthly/yearly)} {target_date? : 集計対象日(YYYY-MM-DD)}';
/**
* コンソールコマンドの説明
*
* @var string
*/
protected $description = 'SHJ-9 売上集計処理 - 日次/月次/年次売上データ集計を実行';
/**
* SHJ-9サービスクラス
*
* @var ShjNineService
*/
protected $shjNineService;
/**
* コンストラクタ
*
* @param ShjNineService $shjNineService
*/
public function __construct(ShjNineService $shjNineService)
{
parent::__construct();
$this->shjNineService = $shjNineService;
}
/**
* コンソールコマンドを実行
*
* 処理フロー:
* 1. パラメータ取得と検証
* 2. 集計対象日設定
* 3. 売上集計処理実行
* 4. バッチログ作成
* 5. 処理結果返却
*
* @return int
*/
public function handle()
{
try {
// 開始ログ出力
$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
{
// 集計種別チェック
$allowedTypes = ['daily', 'monthly', 'yearly'];
if (!in_array($type, $allowedTypes)) {
$this->error('集計種別は daily, monthly, yearly のいずれかを指定してください。');
return false;
}
// 対象日形式チェック(指定されている場合)
if ($targetDate && !$this->isValidDateFormat($targetDate)) {
$this->error('対象日の形式が正しくありませんYYYY-MM-DD形式で指定してください。');
return false;
}
return true;
}
/**
* 集計対象日を決定
*
* @param string $type 集計種別
* @param string|null $targetDate 指定日
* @return string 集計対象日
*/
private function determineAggregationDate(string $type, ?string $targetDate): string
{
if ($targetDate) {
return $targetDate;
}
// パラメータ指定がない場合のデフォルト設定
switch ($type) {
case 'daily':
// 日次昨日本日の1日前
return now()->subDay()->format('Y-m-d');
case 'monthly':
// 月次:前月の最終日
return now()->subMonth()->endOfMonth()->format('Y-m-d');
case 'yearly':
// 年次:前年の最終日
return now()->subYear()->endOfYear()->format('Y-m-d');
default:
return 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;
}
// 実際の日付として有効かチェック
$dateParts = explode('-', $date);
return checkdate((int)$dateParts[1], (int)$dateParts[2], (int)$dateParts[0]);
}
}

View File

@ -0,0 +1,129 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
use App\Services\ShjSixService;
/**
* SHJ-6 サーバ死活監視処理コマンド
*
* サーバとデバイスの死活監視を行い、異常時にはメール通知を実行する
* 定期実行またはオンデマンド実行のバックグラウンドバッチ処理
*/
class ShjSixCommand extends Command
{
/**
* コンソールコマンドの名前とシグネチャ
*
* パラメータなしで実行
*
* @var string
*/
protected $signature = 'shj:6';
/**
* コンソールコマンドの説明
*
* @var string
*/
protected $description = 'SHJ-6 サーバ死活監視処理 - サーバ・デバイス監視とアラート通知を実行';
/**
* SHJ-6サービスクラス
*
* @var ShjSixService
*/
protected $shjSixService;
/**
* コンストラクタ
*
* @param ShjSixService $shjSixService
*/
public function __construct(ShjSixService $shjSixService)
{
parent::__construct();
$this->shjSixService = $shjSixService;
}
/**
* コンソールコマンドを実行
*
* 処理フロー:
* 1. サーバ死活監視DBアクセス
* 2. デバイス管理マスタを取得する
* 3. デバイス毎のハードウェア状態を取得する
* 4. プリンタ制御プログラムログを取得する
* 5. バッチ処理ログを作成する
* 異常検出時は共通A処理メール通知を実行
*
* @return int
*/
public function handle()
{
try {
// 開始ログ出力
$startTime = now();
$this->info('SHJ-6 サーバ死活監視処理を開始します。');
Log::info('SHJ-6 サーバ死活監視処理開始', [
'start_time' => $startTime
]);
// SHJ-6監視処理実行
$result = $this->shjSixService->executeServerMonitoring();
// 処理結果確認
if ($result['success']) {
$endTime = now();
$this->info('SHJ-6 サーバ死活監視処理が正常に完了しました。');
$this->info("処理時間: {$startTime->diffInSeconds($endTime)}");
$this->info("監視結果: {$result['monitoring_summary']}");
// 警告がある場合は表示
if (!empty($result['warnings'])) {
$this->warn('警告が検出されました:');
foreach ($result['warnings'] as $warning) {
$this->warn("- {$warning}");
}
}
Log::info('SHJ-6 サーバ死活監視処理完了', [
'end_time' => $endTime,
'duration_seconds' => $startTime->diffInSeconds($endTime),
'result' => $result
]);
return self::SUCCESS;
} else {
$this->error('SHJ-6 サーバ死活監視処理でエラーが発生しました: ' . $result['message']);
// エラー詳細があれば表示
if (!empty($result['error_details'])) {
$this->error('エラー詳細:');
foreach ($result['error_details'] as $detail) {
$this->error("- {$detail}");
}
}
Log::error('SHJ-6 サーバ死活監視処理エラー', [
'error' => $result['message'],
'details' => $result['error_details'] ?? null
]);
return self::FAILURE;
}
} catch (\Exception $e) {
$this->error('SHJ-6 サーバ死活監視処理で予期しないエラーが発生しました: ' . $e->getMessage());
Log::error('SHJ-6 サーバ死活監視処理例外エラー', [
'exception' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
return self::FAILURE;
}
}
}

View File

@ -0,0 +1,236 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
use App\Services\ShjTenService;
/**
* SHJ-10 売上集計処理コマンド
*
* 駐輪場の売上データを財政年度ベースで年次・月次集計する処理を実行する
* 4月開始の財政年度期間で計算するバックグラウンドバッチ処理
*/
class ShjTenCommand extends Command
{
/**
* コンソールコマンドの名前とシグネチャ
*
* 引数:
* - type: 集計種別 (yearly/monthly) (必須)
* - target: 集計対象 (必須)
* - yearly: 年度 (: 2019)
* - monthly: 年月 (: 2019/01)
*
* @var string
*/
protected $signature = 'shj:10 {type : 集計種別(yearly/monthly)} {target : 集計対象(yearly:年度, monthly:年月)}';
/**
* コンソールコマンドの説明
*
* @var string
*/
protected $description = 'SHJ-10 売上集計処理 - 財政年度ベース年次/月次売上データ集計を実行';
/**
* SHJ-10サービスクラス
*
* @var ShjTenService
*/
protected $shjTenService;
/**
* コンストラクタ
*
* @param ShjTenService $shjTenService
*/
public function __construct(ShjTenService $shjTenService)
{
parent::__construct();
$this->shjTenService = $shjTenService;
}
/**
* コンソールコマンドを実行
*
* 処理フロー:
* 1. パラメータ取得と検証
* 2. 財政年度期間設定
* 3. 売上集計処理実行
* 4. バッチログ作成
* 5. 処理結果返却
*
* @return int
*/
public function handle()
{
try {
// 開始ログ出力
$startTime = now();
$this->info('SHJ-10 売上集計処理を開始します。');
// 引数取得
$type = $this->argument('type');
$target = $this->argument('target');
Log::info('SHJ-10 売上集計処理開始', [
'start_time' => $startTime,
'type' => $type,
'target' => $target
]);
// パラメータ検証
if (!$this->validateParameters($type, $target)) {
$this->error('パラメータが不正です。');
return self::FAILURE;
}
// 財政年度期間設定
$fiscalPeriod = $this->determineFiscalPeriod($type, $target);
$this->info("集計種別: {$type}");
$this->info("集計対象: {$target}");
$this->info("財政期間: {$fiscalPeriod['start_date']} {$fiscalPeriod['end_date']}");
// SHJ-10処理実行
$result = $this->shjTenService->executeFiscalEarningsAggregation($type, $target, $fiscalPeriod);
// 処理結果確認
if ($result['success']) {
$endTime = now();
$this->info('SHJ-10 売上集計処理が正常に完了しました。');
$this->info("処理時間: {$startTime->diffInSeconds($endTime)}");
$this->info("処理結果: 駐輪場数 {$result['processed_parks']}, 集計レコード数 {$result['summary_records']}");
Log::info('SHJ-10 売上集計処理完了', [
'end_time' => $endTime,
'duration_seconds' => $startTime->diffInSeconds($endTime),
'result' => $result
]);
return self::SUCCESS;
} else {
$this->error('SHJ-10 売上集計処理でエラーが発生しました: ' . $result['message']);
Log::error('SHJ-10 売上集計処理エラー', [
'error' => $result['message'],
'details' => $result['details'] ?? null
]);
return self::FAILURE;
}
} catch (\Exception $e) {
$this->error('SHJ-10 売上集計処理で予期しないエラーが発生しました: ' . $e->getMessage());
Log::error('SHJ-10 売上集計処理例外エラー', [
'exception' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
return self::FAILURE;
}
}
/**
* パラメータの妥当性を検証
*
* @param string $type 集計種別
* @param string $target 集計対象
* @return bool 検証結果
*/
private function validateParameters(string $type, string $target): bool
{
// 集計種別チェック
$allowedTypes = ['yearly', 'monthly'];
if (!in_array($type, $allowedTypes)) {
$this->error('集計種別は yearly, monthly のいずれかを指定してください。');
return false;
}
// 集計対象形式チェック
if ($type === 'yearly') {
// 年度形式チェック (例: 2019)
if (!preg_match('/^\d{4}$/', $target)) {
$this->error('年次集計の場合、年度を4桁の数字で指定してください。(例: 2019)');
return false;
}
} elseif ($type === 'monthly') {
// 年月形式チェック (例: 2019/01)
if (!preg_match('/^\d{4}\/\d{2}$/', $target)) {
$this->error('月次集計の場合、年月をYYYY/MM形式で指定してください。(例: 2019/01)');
return false;
}
// 月の範囲チェック (01-12)
$parts = explode('/', $target);
$month = (int)$parts[1];
if ($month < 1 || $month > 12) {
$this->error('月は01から12までの範囲で指定してください。');
return false;
}
}
return true;
}
/**
* 財政年度期間を決定
*
* 財政年度は4月開始Config設定可能
* - yearly 2019: 2019年4月1日 2020年3月31日
* - monthly 2019/01: 2019年1月1日 2019年1月31日
*
* @param string $type 集計種別
* @param string $target 集計対象
* @return array 財政期間情報
*/
private function determineFiscalPeriod(string $type, string $target): array
{
$fiscalStartMonth = 4; // 財政年度開始月4月
if ($type === 'yearly') {
$year = (int)$target;
// 財政年度期間計算
$startDate = sprintf('%04d-%02d-01', $year, $fiscalStartMonth);
$endDate = sprintf('%04d-%02d-%02d', $year + 1, $fiscalStartMonth - 1,
date('t', strtotime(sprintf('%04d-%02d-01', $year + 1, $fiscalStartMonth - 1))));
return [
'type' => 'yearly',
'fiscal_year' => $year,
'start_date' => $startDate,
'end_date' => $endDate,
'summary_type' => 1, // 年次
'target_label' => "{$year}年度"
];
} elseif ($type === 'monthly') {
$parts = explode('/', $target);
$year = (int)$parts[0];
$month = (int)$parts[1];
// 指定月の期間計算
$startDate = sprintf('%04d-%02d-01', $year, $month);
$endDate = sprintf('%04d-%02d-%02d', $year, $month,
date('t', strtotime($startDate)));
// 該当する財政年度を計算
$fiscalYear = $month >= $fiscalStartMonth ? $year : $year - 1;
return [
'type' => 'monthly',
'fiscal_year' => $fiscalYear,
'target_year' => $year,
'target_month' => $month,
'start_date' => $startDate,
'end_date' => $endDate,
'summary_type' => 2, // 月次
'target_label' => "{$year}{$month}"
];
}
throw new \InvalidArgumentException("不正な集計種別: {$type}");
}
}

View File

@ -24,3 +24,5 @@ enum QueueClass: string
}

View File

@ -15,3 +15,5 @@ enum QueueStatus: string
}

View File

@ -85,3 +85,5 @@ class OperatorQue extends Model
}

View File

@ -56,3 +56,5 @@ class Park extends Model
}

View File

@ -112,3 +112,5 @@ class User extends Model
}

View File

@ -19,3 +19,4 @@ abstract class BaseModel extends Model
}

View File

@ -38,3 +38,5 @@ trait HasSortable
}

View File

@ -0,0 +1,298 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Carbon\Carbon;
/**
* 売上集計結果モデル - earnings_summaryテーブル
*
* SHJ-9で作成される日次・月次・年次の売上集計データを管理
*/
class EarningsSummary extends Model
{
/**
* テーブル名
*
* @var string
*/
protected $table = 'earnings_summary';
/**
* プライマリキー
*
* @var string
*/
protected $primaryKey = 'earnings_summary_id';
/**
* 一括代入可能な属性
*
* @var array
*/
protected $fillable = [
'park_id', // 駐輪場ID
'summary_type', // 集計区分
'summary_start_date', // 集計開始日
'summary_end_date', // 集計終了日
'earnings_date', // 売上日
'psection_id', // 車種区分ID
'usertype_subject1', // 規格
'enable_months', // 期間(月数)
'regular_new_count', // 期間件数
'regular_new_amount', // 期間金額
'regular_new_reduction_count', // 期間成免件数
'regular_new_reduction_amount', // 期間成免金額
'regular_update_count', // 更新件数
'regular_update_amount', // 更新金額
'regular_update_reduction_count', // 更新成免件数
'regular_update_reduction_amount', // 更新成免金額
'turnsum_count', // 残金件数
'turnsum', // 残金
'refunds', // 解時返戻金
'other_income', // 分別収入
'other_spending', // 分別支出
'reissue_count', // 発行件数
'reissue_amount', // 発行金額
'summary_note', // 計備考
'created_at', // 登録日時
'updated_at', // 更新日時
'operator_id' // 新法・ページID
];
/**
* キャストする属性
*
* @var array
*/
protected $casts = [
'earnings_summary_id' => 'integer',
'park_id' => 'integer',
'psection_id' => 'integer',
'enable_months' => 'integer',
'regular_new_count' => 'integer',
'regular_new_amount' => 'decimal:2',
'regular_new_reduction_count' => 'integer',
'regular_new_reduction_amount' => 'decimal:2',
'regular_update_count' => 'integer',
'regular_update_amount' => 'decimal:2',
'regular_update_reduction_count' => 'integer',
'regular_update_reduction_amount' => 'decimal:2',
'turnsum_count' => 'integer',
'turnsum' => 'decimal:2',
'refunds' => 'decimal:2',
'other_income' => 'decimal:2',
'other_spending' => 'decimal:2',
'reissue_count' => 'integer',
'reissue_amount' => 'decimal:2',
'operator_id' => 'integer',
'summary_start_date' => 'date',
'summary_end_date' => 'date',
'earnings_date' => 'date',
'created_at' => 'datetime',
'updated_at' => 'datetime'
];
/**
* 日付属性
*
* @var array
*/
protected $dates = [
'summary_start_date',
'summary_end_date',
'earnings_date',
'created_at',
'updated_at'
];
/**
* 集計タイプの定数
*/
const TYPE_DAILY = '日次';
const TYPE_MONTHLY = '月次';
const TYPE_YEARLY = '年次';
/**
* 駐輪場との関連
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function park()
{
return $this->belongsTo(Park::class, 'park_id', 'park_id');
}
/**
* 車種区分との関連
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function psection()
{
return $this->belongsTo(Psection::class, 'psection_id', 'psection_id');
}
/**
* 指定期間の売上集計データを取得
*
* @param int $parkId 駐輪場ID
* @param string $startDate 開始日
* @param string $endDate 終了日
* @param string|null $summaryType 集計タイプ
* @return \Illuminate\Database\Eloquent\Collection
*/
public static function getEarningsByPeriod(int $parkId, string $startDate, string $endDate, ?string $summaryType = null)
{
$query = self::where('park_id', $parkId)
->whereBetween('earnings_date', [$startDate, $endDate]);
if ($summaryType) {
$query->where('summary_type', $summaryType);
}
return $query->with(['park', 'psection'])
->orderBy('earnings_date')
->orderBy('psection_id')
->get();
}
/**
* 駐輪場別の売上合計を取得
*
* @param int $parkId 駐輪場ID
* @param string $startDate 開始日
* @param string $endDate 終了日
* @return array 売上合計データ
*/
public static function getEarningsTotalByPark(int $parkId, string $startDate, string $endDate): array
{
$result = self::where('park_id', $parkId)
->whereBetween('earnings_date', [$startDate, $endDate])
->selectRaw('
SUM(regular_new_count) as total_new_count,
SUM(regular_new_amount) as total_new_amount,
SUM(regular_update_count) as total_update_count,
SUM(regular_update_amount) as total_update_amount,
SUM(turnsum_count) as total_turnsum_count,
SUM(turnsum) as total_turnsum,
SUM(refunds) as total_refunds,
SUM(reissue_count) as total_reissue_count,
SUM(reissue_amount) as total_reissue_amount
')
->first();
return $result ? $result->toArray() : [];
}
/**
* 最新の集計日を取得
*
* @param int|null $parkId 駐輪場ID省略時は全体
* @param string|null $summaryType 集計タイプ
* @return string|null 最新集計日
*/
public static function getLatestEarningsDate(?int $parkId = null, ?string $summaryType = null): ?string
{
$query = self::query();
if ($parkId) {
$query->where('park_id', $parkId);
}
if ($summaryType) {
$query->where('summary_type', $summaryType);
}
$latest = $query->max('earnings_date');
return $latest ? Carbon::parse($latest)->format('Y-m-d') : null;
}
/**
* 期間の売上データを削除
*
* @param int $parkId 駐輪場ID
* @param string $startDate 開始日
* @param string $endDate 終了日
* @param string|null $summaryType 集計タイプ
* @return int 削除件数
*/
public static function deleteEarningsByPeriod(int $parkId, string $startDate, string $endDate, ?string $summaryType = null): int
{
$query = self::where('park_id', $parkId)
->where('summary_start_date', $startDate)
->where('summary_end_date', $endDate);
if ($summaryType) {
$query->where('summary_type', $summaryType);
}
return $query->delete();
}
/**
* 売上集計データの作成
*
* @param array $data 売上データ
* @return EarningsSummary 作成されたモデル
*/
public static function createEarningsSummary(array $data): EarningsSummary
{
$defaultData = [
'regular_new_count' => 0,
'regular_new_amount' => 0.00,
'regular_new_reduction_count' => 0,
'regular_new_reduction_amount' => 0.00,
'regular_update_count' => 0,
'regular_update_amount' => 0.00,
'regular_update_reduction_count' => 0,
'regular_update_reduction_amount' => 0.00,
'turnsum_count' => 0,
'turnsum' => 0.00,
'refunds' => 0.00,
'other_income' => 0.00,
'other_spending' => 0.00,
'reissue_count' => 0,
'reissue_amount' => 0.00,
'operator_id' => 0
];
$mergedData = array_merge($defaultData, $data);
return self::create($mergedData);
}
/**
* 売上合計の計算
*
* @return float 売上合計
*/
public function getTotalEarningsAttribute(): float
{
return $this->regular_new_amount +
$this->regular_update_amount +
$this->turnsum +
$this->reissue_amount +
$this->other_income -
$this->other_spending -
$this->refunds;
}
/**
* 文字列表現
*
* @return string
*/
public function __toString(): string
{
return sprintf(
'EarningsSummary[ID:%d, Park:%d, Type:%s, Date:%s]',
$this->earnings_summary_id,
$this->park_id,
$this->summary_type,
$this->earnings_date ? $this->earnings_date->format('Y-m-d') : 'N/A'
);
}
}

View File

@ -0,0 +1,229 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Carbon\Carbon;
/**
* ハードウェアチェックログモデル - hardware_check_logテーブル
*
* デバイスのハードウェア状態監視ログを管理
*/
class HardwareCheckLog extends Model
{
/**
* テーブル名
*
* @var string
*/
protected $table = 'hardware_check_log';
/**
* プライマリキー
*
* @var string
*/
protected $primaryKey = 'log_id';
/**
* 一括代入可能な属性
*
* @var array
*/
protected $fillable = [
'device_id', // デバイスID
'status', // ステータス
'status_comment', // ステータスコメント
'created_at', // 作成日時
'updated_at', // 更新日時
'operator_id' // オペレータID
];
/**
* キャストする属性
*
* @var array
*/
protected $casts = [
'log_id' => 'integer',
'device_id' => 'integer',
'status' => 'integer',
'operator_id' => 'integer',
'created_at' => 'datetime',
'updated_at' => 'datetime'
];
/**
* ステータスの定数
*/
const STATUS_NORMAL = 1; // 正常
const STATUS_WARNING = 2; // 警告
const STATUS_ERROR = 3; // エラー
const STATUS_UNKNOWN = 0; // 不明
/**
* デバイスとの関連
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function device()
{
return $this->belongsTo(Device::class, 'device_id', 'device_id');
}
/**
* 指定デバイスの最新ハードウェア状態を取得
*
* @param int $deviceId デバイスID
* @return HardwareCheckLog|null 最新ログ
*/
public static function getLatestStatusByDevice(int $deviceId): ?HardwareCheckLog
{
return self::where('device_id', $deviceId)
->orderBy('created_at', 'desc')
->first();
}
/**
* 全デバイスの最新ハードウェア状態を取得
*
* @return \Illuminate\Database\Eloquent\Collection
*/
public static function getLatestStatusForAllDevices()
{
return self::select('device_id')
->selectRaw('MAX(created_at) as latest_created_at')
->groupBy('device_id')
->with(['device'])
->get()
->map(function ($log) {
return self::where('device_id', $log->device_id)
->where('created_at', $log->latest_created_at)
->with(['device'])
->first();
})
->filter();
}
/**
* 異常状態のデバイスを取得
*
* @return \Illuminate\Database\Eloquent\Collection
*/
public static function getAbnormalDevices()
{
$latestLogs = self::getLatestStatusForAllDevices();
return $latestLogs->filter(function ($log) {
return $log->status !== self::STATUS_NORMAL;
});
}
/**
* 指定期間内のログを取得
*
* @param int $deviceId デバイスID
* @param string $startTime 開始時刻
* @param string $endTime 終了時刻
* @return \Illuminate\Database\Eloquent\Collection
*/
public static function getLogsByPeriod(int $deviceId, string $startTime, string $endTime)
{
return self::where('device_id', $deviceId)
->whereBetween('created_at', [$startTime, $endTime])
->orderBy('created_at', 'desc')
->get();
}
/**
* ハードウェア状態ログを作成
*
* @param int $deviceId デバイスID
* @param int $status ステータス
* @param string $statusComment ステータスコメント
* @param int|null $operatorId オペレータID
* @return HardwareCheckLog 作成されたログ
*/
public static function createLog(
int $deviceId,
int $status,
string $statusComment = '',
?int $operatorId = null
): HardwareCheckLog {
return self::create([
'device_id' => $deviceId,
'status' => $status,
'status_comment' => $statusComment,
'operator_id' => $operatorId ?? 0
]);
}
/**
* ステータス名を取得
*
* @param int $status ステータス
* @return string ステータス名
*/
public static function getStatusName(int $status): string
{
switch ($status) {
case self::STATUS_NORMAL:
return '正常';
case self::STATUS_WARNING:
return '警告';
case self::STATUS_ERROR:
return 'エラー';
case self::STATUS_UNKNOWN:
return '不明';
default:
return "ステータス{$status}";
}
}
/**
* 現在のステータス名を取得
*
* @return string ステータス名
*/
public function getStatusNameAttribute(): string
{
return self::getStatusName($this->status);
}
/**
* 正常状態かどうかを判定
*
* @return bool 正常状態かどうか
*/
public function isNormal(): bool
{
return $this->status === self::STATUS_NORMAL;
}
/**
* 異常状態かどうかを判定
*
* @return bool 異常状態かどうか
*/
public function isAbnormal(): bool
{
return $this->status !== self::STATUS_NORMAL;
}
/**
* 文字列表現
*
* @return string
*/
public function __toString(): string
{
return sprintf(
'HardwareCheckLog[ID:%d, Device:%d, Status:%s, Time:%s]',
$this->log_id,
$this->device_id,
$this->getStatusNameAttribute(),
$this->created_at ? $this->created_at->format('Y-m-d H:i:s') : 'N/A'
);
}
}

293
app/Models/OperatorQue.php Normal file
View File

@ -0,0 +1,293 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
/**
* オペレータキューモデル - operator_queテーブル
*
* バッチ処理結果の通知や作業指示を管理
*/
class OperatorQue extends Model
{
/**
* テーブル名
*
* @var string
*/
protected $table = 'operator_que';
/**
* プライマリキー
*
* @var string
*/
protected $primaryKey = 'que_id';
/**
* 一括代入可能な属性
*
* @var array
*/
protected $fillable = [
'que_class', // キュークラス
'user_id', // ユーザーID
'contract_id', // 契約ID
'park_id', // 駐輪場ID
'que_comment', // キューコメント
'que_status', // キューステータス
'que_status_comment', // キューステータスコメント
'work_instructions', // 作業指示
'created_at', // 作成日時
'updated_at' // 更新日時
];
/**
* キャストする属性
*
* @var array
*/
protected $casts = [
'que_id' => 'integer',
'que_class' => 'integer',
'user_id' => 'integer',
'contract_id' => 'integer',
'park_id' => 'integer',
'que_status' => 'integer',
'created_at' => 'datetime',
'updated_at' => 'datetime'
];
/**
* キュークラスの定数
*/
const CLASS_SHJ4C = 4; // SHJ-4C室割当処理
const CLASS_SHJ6 = 6; // SHJ-6サーバ死活監視処理
const CLASS_SHJ8 = 8; // SHJ-8バッチログ処理
const CLASS_SHJ9 = 9; // SHJ-9売上集計処理
const CLASS_SHJ10 = 10; // SHJ-10財政年度売上集計処理
const CLASS_MAIL_SEND = 11; // メール送信処理
/**
* キューステータスの定数
*/
const STATUS_PENDING = 0; // 待機中
const STATUS_COMPLETED = 1; // 完了
const STATUS_ERROR = 2; // エラー
const STATUS_CANCELLED = 3; // キャンセル
/**
* 駐輪場との関連
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function park()
{
return $this->belongsTo(Park::class, 'park_id', 'park_id');
}
/**
* ユーザーとの関連
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function user()
{
return $this->belongsTo(User::class, 'user_id', 'user_id');
}
/**
* 契約との関連
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function contract()
{
return $this->belongsTo(RegularContract::class, 'contract_id', 'contract_id');
}
/**
* バッチ処理用キューを作成
*
* @param int $queClass キュークラス
* @param string $comment コメント
* @param int $status ステータス
* @param string|null $workInstructions 作業指示
* @param int|null $parkId 駐輪場ID
* @return OperatorQue 作成されたキュー
*/
public static function createBatchQueue(
int $queClass,
string $comment,
int $status = self::STATUS_COMPLETED,
?string $workInstructions = null,
?int $parkId = null
): OperatorQue {
return self::create([
'que_class' => $queClass,
'user_id' => null,
'contract_id' => null,
'park_id' => $parkId,
'que_comment' => $comment,
'que_status' => $status,
'que_status_comment' => self::getStatusComment($status),
'work_instructions' => $workInstructions
]);
}
/**
* SHJ-9用キューを作成
*
* @param string $message メッセージ
* @param int $batchLogId バッチログID
* @param int $status ステータス
* @return OperatorQue 作成されたキュー
*/
public static function createShjNineQueue(
string $message,
int $batchLogId,
int $status = self::STATUS_COMPLETED
): OperatorQue {
return self::createBatchQueue(
self::CLASS_SHJ9,
$message,
$status,
"SHJ-9売上集計処理 BatchLogID: {$batchLogId}"
);
}
/**
* 指定期間のキューを取得
*
* @param string $startDate 開始日
* @param string $endDate 終了日
* @param int|null $queClass キュークラス
* @return \Illuminate\Database\Eloquent\Collection
*/
public static function getQueuesByPeriod(string $startDate, string $endDate, ?int $queClass = null)
{
$query = self::whereBetween('created_at', [$startDate, $endDate]);
if ($queClass) {
$query->where('que_class', $queClass);
}
return $query->with(['park', 'user', 'contract'])
->orderBy('created_at', 'desc')
->get();
}
/**
* 未完了のキューを取得
*
* @param int|null $queClass キュークラス
* @return \Illuminate\Database\Eloquent\Collection
*/
public static function getPendingQueues(?int $queClass = null)
{
$query = self::where('que_status', self::STATUS_PENDING);
if ($queClass) {
$query->where('que_class', $queClass);
}
return $query->with(['park', 'user', 'contract'])
->orderBy('created_at')
->get();
}
/**
* キューを完了状態に更新
*
* @param string|null $comment 完了コメント
* @return bool 更新結果
*/
public function markAsCompleted(?string $comment = null): bool
{
return $this->update([
'que_status' => self::STATUS_COMPLETED,
'que_status_comment' => $comment ?? self::getStatusComment(self::STATUS_COMPLETED),
'updated_at' => now()
]);
}
/**
* キューをエラー状態に更新
*
* @param string $errorMessage エラーメッセージ
* @return bool 更新結果
*/
public function markAsError(string $errorMessage): bool
{
return $this->update([
'que_status' => self::STATUS_ERROR,
'que_status_comment' => $errorMessage,
'updated_at' => now()
]);
}
/**
* ステータスコメントを取得
*
* @param int $status ステータス
* @return string ステータスコメント
*/
public static function getStatusComment(int $status): string
{
switch ($status) {
case self::STATUS_PENDING:
return '待機中';
case self::STATUS_COMPLETED:
return '完了';
case self::STATUS_ERROR:
return 'エラー';
case self::STATUS_CANCELLED:
return 'キャンセル';
default:
return '不明';
}
}
/**
* キュークラス名を取得
*
* @param int $queClass キュークラス
* @return string クラス名
*/
public static function getClassName(int $queClass): string
{
switch ($queClass) {
case self::CLASS_SHJ4C:
return 'SHJ-4C室割当処理';
case self::CLASS_SHJ6:
return 'SHJ-6サーバ死活監視処理';
case self::CLASS_SHJ8:
return 'SHJ-8バッチログ処理';
case self::CLASS_SHJ9:
return 'SHJ-9売上集計処理';
case self::CLASS_SHJ10:
return 'SHJ-10財政年度売上集計処理';
case self::CLASS_MAIL_SEND:
return 'メール送信処理';
default:
return "クラス{$queClass}";
}
}
/**
* 文字列表現
*
* @return string
*/
public function __toString(): string
{
return sprintf(
'OperatorQue[ID:%d, Class:%s, Status:%s, Date:%s]',
$this->que_id,
self::getClassName($this->que_class),
self::getStatusComment($this->que_status),
$this->created_at ? $this->created_at->format('Y-m-d H:i:s') : 'N/A'
);
}
}

View File

@ -42,3 +42,5 @@ class Park extends Model
}

View File

@ -58,3 +58,5 @@ class PriceA extends Model
}

251
app/Models/PrintJobLog.php Normal file
View File

@ -0,0 +1,251 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Carbon\Carbon;
/**
* プリンタジョブログモデル - print_job_logテーブル
*
* プリンタ制御プログラムの実行ログを管理
*/
class PrintJobLog extends Model
{
/**
* テーブル名
*
* @var string
*/
protected $table = 'print_job_log';
/**
* プライマリキー
*
* @var string
*/
protected $primaryKey = 'log_id';
/**
* 一括代入可能な属性
*
* @var array
*/
protected $fillable = [
'park_id', // 駐輪場ID
'user_id', // ユーザーID
'contract_id', // 契約ID
'process_name', // プロセス名
'job_name', // ジョブ名
'status', // ステータス
'error_code', // エラーコード
'status_comment', // ステータスコメント
'created_at' // 作成日時
];
/**
* キャストする属性
*
* @var array
*/
protected $casts = [
'log_id' => 'integer',
'park_id' => 'integer',
'user_id' => 'integer',
'contract_id' => 'integer',
'error_code' => 'integer',
'created_at' => 'datetime'
];
/**
* エラーコードの定数
*/
const ERROR_CODE_THRESHOLD = 100; // エラー判定閾値(>=100がエラー
/**
* updateされない設定created_atのみ
*
* @var bool
*/
public $timestamps = false;
/**
* 駐輪場との関連
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function park()
{
return $this->belongsTo(Park::class, 'park_id', 'park_id');
}
/**
* ユーザーとの関連
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function user()
{
return $this->belongsTo(User::class, 'user_id', 'user_id');
}
/**
* 契約との関連
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function contract()
{
return $this->belongsTo(RegularContract::class, 'contract_id', 'contract_id');
}
/**
* 過去15分間のエラーログを取得
*
* @return \Illuminate\Database\Eloquent\Collection
*/
public static function getRecentErrorLogs()
{
$fifteenMinutesAgo = Carbon::now()->subMinutes(15);
return self::where('created_at', '>=', $fifteenMinutesAgo)
->where('error_code', '>=', self::ERROR_CODE_THRESHOLD)
->orderBy('created_at', 'desc')
->get();
}
/**
* 指定期間内のエラーログを取得
*
* @param string $startTime 開始時刻
* @param string $endTime 終了時刻
* @return \Illuminate\Database\Eloquent\Collection
*/
public static function getErrorLogsByPeriod(string $startTime, string $endTime)
{
return self::whereBetween('created_at', [$startTime, $endTime])
->where('error_code', '>=', self::ERROR_CODE_THRESHOLD)
->orderBy('created_at', 'desc')
->get();
}
/**
* 指定期間内の全ログを取得
*
* @param string $startTime 開始時刻
* @param string $endTime 終了時刻
* @return \Illuminate\Database\Eloquent\Collection
*/
public static function getLogsByPeriod(string $startTime, string $endTime)
{
return self::whereBetween('created_at', [$startTime, $endTime])
->orderBy('created_at', 'desc')
->get();
}
/**
* プリンタジョブログを作成
*
* @param array $data ログデータ
* @return PrintJobLog 作成されたログ
*/
public static function createLog(array $data): PrintJobLog
{
$defaultData = [
'park_id' => null,
'user_id' => null,
'contract_id' => null,
'process_name' => '',
'job_name' => '',
'status' => '',
'error_code' => 0,
'status_comment' => '',
'created_at' => now()
];
$mergedData = array_merge($defaultData, $data);
return self::create($mergedData);
}
/**
* エラーログかどうかを判定
*
* @return bool エラーログかどうか
*/
public function isError(): bool
{
return $this->error_code >= self::ERROR_CODE_THRESHOLD;
}
/**
* 正常ログかどうかを判定
*
* @return bool 正常ログかどうか
*/
public function isNormal(): bool
{
return $this->error_code < self::ERROR_CODE_THRESHOLD;
}
/**
* エラーレベルを取得
*
* @return string エラーレベル
*/
public function getErrorLevel(): string
{
if ($this->error_code >= 200) {
return '重大エラー';
} elseif ($this->error_code >= self::ERROR_CODE_THRESHOLD) {
return 'エラー';
} else {
return '正常';
}
}
/**
* エラーログの統計情報を取得
*
* @param string $startTime 開始時刻
* @param string $endTime 終了時刻
* @return array 統計情報
*/
public static function getErrorStatistics(string $startTime, string $endTime): array
{
$errorLogs = self::getErrorLogsByPeriod($startTime, $endTime);
$totalLogs = self::getLogsByPeriod($startTime, $endTime);
$errorByCode = $errorLogs->groupBy('error_code')->map->count();
$errorByProcess = $errorLogs->groupBy('process_name')->map->count();
return [
'total_logs' => $totalLogs->count(),
'error_logs' => $errorLogs->count(),
'error_rate' => $totalLogs->count() > 0 ?
round(($errorLogs->count() / $totalLogs->count()) * 100, 2) : 0,
'error_by_code' => $errorByCode->toArray(),
'error_by_process' => $errorByProcess->toArray(),
'period' => [
'start' => $startTime,
'end' => $endTime
]
];
}
/**
* 文字列表現
*
* @return string
*/
public function __toString(): string
{
return sprintf(
'PrintJobLog[ID:%d, Process:%s, ErrorCode:%d, Time:%s]',
$this->log_id,
$this->process_name,
$this->error_code,
$this->created_at ? $this->created_at->format('Y-m-d H:i:s') : 'N/A'
);
}
}

106
app/Models/Psection.php Normal file
View File

@ -0,0 +1,106 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
/**
* 車種区分モデル - psectionテーブル
*
* 駐輪場の車種区分マスタデータを管理
*/
class Psection extends Model
{
/**
* テーブル名
*
* @var string
*/
protected $table = 'psection';
/**
* プライマリキー
*
* @var string
*/
protected $primaryKey = 'psection_id';
/**
* 一括代入可能な属性
*
* @var array
*/
protected $fillable = [
'psection_subject', // 車種区分名
'operator_id', // オペレータID
'created_at', // 作成日時
'updated_at' // 更新日時
];
/**
* キャストする属性
*
* @var array
*/
protected $casts = [
'psection_id' => 'integer',
'operator_id' => 'integer',
'created_at' => 'datetime',
'updated_at' => 'datetime'
];
/**
* 売上集計との関連
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function earningsSummaries()
{
return $this->hasMany(EarningsSummary::class, 'psection_id', 'psection_id');
}
/**
* 定期契約との関連
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function regularContracts()
{
return $this->hasMany(RegularContract::class, 'psection_id', 'psection_id');
}
/**
* アクティブな車種区分一覧を取得
*
* @return \Illuminate\Database\Eloquent\Collection
*/
public static function getActivePsections()
{
return self::orderBy('psection_id')->get();
}
/**
* 車種区分名で検索
*
* @param string $subject 車種区分名
* @return Psection|null
*/
public static function findBySubject(string $subject): ?Psection
{
return self::where('psection_subject', $subject)->first();
}
/**
* 文字列表現
*
* @return string
*/
public function __toString(): string
{
return sprintf(
'Psection[ID:%d, Subject:%s]',
$this->psection_id,
$this->psection_subject
);
}
}

View File

@ -33,3 +33,5 @@ class Ptype extends Model
}

View File

@ -72,3 +72,5 @@ class RegularContract extends Model
}

View File

@ -5,6 +5,9 @@ namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Services\ShjFourCService;
use App\Services\ShjMailSendService;
use App\Services\ShjNineService;
use App\Services\ShjTenService;
use App\Services\ShjSixService;
class AppServiceProvider extends ServiceProvider
{
@ -30,7 +33,41 @@ class AppServiceProvider extends ServiceProvider
);
});
// SHJ-9売上集計処理サービスを登録
$this->app->singleton(ShjNineService::class, function ($app) {
return new ShjNineService(
$app->make(\App\Models\Park::class),
$app->make(\App\Models\RegularContract::class),
$app->make(\App\Models\EarningsSummary::class),
$app->make(\App\Models\Psection::class),
$app->make(\App\Models\Batch\BatchLog::class),
$app->make(\App\Models\OperatorQue::class)
);
});
// SHJ-10財政年度売上集計処理サービスを登録
$this->app->singleton(ShjTenService::class, function ($app) {
return new ShjTenService(
$app->make(\App\Models\Park::class),
$app->make(\App\Models\RegularContract::class),
$app->make(\App\Models\EarningsSummary::class),
$app->make(\App\Models\Psection::class),
$app->make(\App\Models\Batch\BatchLog::class),
$app->make(\App\Models\OperatorQue::class)
);
});
// SHJ-6サーバ死活監視処理サービスを登録
$this->app->singleton(ShjSixService::class, function ($app) {
return new ShjSixService(
$app->make(\App\Models\Device::class),
$app->make(\App\Models\HardwareCheckLog::class),
$app->make(\App\Models\PrintJobLog::class),
$app->make(\App\Models\Batch\BatchLog::class),
$app->make(\App\Models\OperatorQue::class),
$app->make(\App\Services\ShjMailSendService::class)
);
});
}
/**

View File

@ -42,3 +42,4 @@ class LegacyServiceProvider extends ServiceProvider
}

View File

@ -32,3 +32,5 @@ class FileService
}

View File

@ -25,3 +25,5 @@ class OperatorQueService
}

View File

@ -0,0 +1,595 @@
<?php
namespace App\Services;
use App\Models\Park;
use App\Models\RegularContract;
use App\Models\EarningsSummary;
use App\Models\Psection;
use App\Models\Batch\BatchLog;
use App\Models\OperatorQue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\DB;
use Carbon\Carbon;
/**
* SHJ-9 売上集計処理サービス
*
* 日次・月次・年次の売上集計処理を実行するビジネスロジック
* バッチ処理「SHJ-9売上集計」の核となる処理を担当
*/
class ShjNineService
{
/**
* Park モデル
*
* @var Park
*/
protected $parkModel;
/**
* RegularContract モデル
*
* @var RegularContract
*/
protected $contractModel;
/**
* EarningsSummary モデル
*
* @var EarningsSummary
*/
protected $earningsSummaryModel;
/**
* Psection モデル
*
* @var Psection
*/
protected $psectionModel;
/**
* BatchLog モデル
*
* @var BatchLog
*/
protected $batchLogModel;
/**
* OperatorQue モデル
*
* @var OperatorQue
*/
protected $operatorQueModel;
/**
* コンストラクタ
*
* @param Park $parkModel
* @param RegularContract $contractModel
* @param EarningsSummary $earningsSummaryModel
* @param Psection $psectionModel
* @param BatchLog $batchLogModel
* @param OperatorQue $operatorQueModel
*/
public function __construct(
Park $parkModel,
RegularContract $contractModel,
EarningsSummary $earningsSummaryModel,
Psection $psectionModel,
BatchLog $batchLogModel,
OperatorQue $operatorQueModel
) {
$this->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;
}
}
}

View File

@ -0,0 +1,710 @@
<?php
namespace App\Services;
use App\Models\Device;
use App\Models\HardwareCheckLog;
use App\Models\PrintJobLog;
use App\Models\Batch\BatchLog;
use App\Models\OperatorQue;
use App\Models\User;
use App\Models\Park;
use App\Services\ShjMailSendService;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\DB;
use Carbon\Carbon;
/**
* SHJ-6 サーバ死活監視処理サービス
*
* サーバとデバイスの死活監視、ハードウェア状態監視、プリンタログ監視を実行
* 異常検出時のメール通知機能を含む統合モニタリングサービス
*/
class ShjSixService
{
/**
* Device モデル
*
* @var Device
*/
protected $deviceModel;
/**
* HardwareCheckLog モデル
*
* @var HardwareCheckLog
*/
protected $hardwareCheckLogModel;
/**
* PrintJobLog モデル
*
* @var PrintJobLog
*/
protected $printJobLogModel;
/**
* BatchLog モデル
*
* @var BatchLog
*/
protected $batchLogModel;
/**
* OperatorQue モデル
*
* @var OperatorQue
*/
protected $operatorQueModel;
/**
* ShjMailSendService
*
* @var ShjMailSendService
*/
protected $mailSendService;
/**
* 固定メールアドレスDB反映NG時用
*
* @var string
*/
const FIXED_EMAIL_ADDRESS = 'system-alert@so-manager.com';
/**
* プリンタログ監視期間(分)
*
* @var int
*/
const PRINTER_LOG_MONITOR_MINUTES = 15;
/**
* コンストラクタ
*
* @param Device $deviceModel
* @param HardwareCheckLog $hardwareCheckLogModel
* @param PrintJobLog $printJobLogModel
* @param BatchLog $batchLogModel
* @param OperatorQue $operatorQueModel
* @param ShjMailSendService $mailSendService
*/
public function __construct(
Device $deviceModel,
HardwareCheckLog $hardwareCheckLogModel,
PrintJobLog $printJobLogModel,
BatchLog $batchLogModel,
OperatorQue $operatorQueModel,
ShjMailSendService $mailSendService
) {
$this->deviceModel = $deviceModel;
$this->hardwareCheckLogModel = $hardwareCheckLogModel;
$this->printJobLogModel = $printJobLogModel;
$this->batchLogModel = $batchLogModel;
$this->operatorQueModel = $operatorQueModel;
$this->mailSendService = $mailSendService;
}
/**
* SHJ-6 サーバ死活監視処理メイン実行
*
* 処理フロー:
* 【処理1】サーバ死活監視DBアクセス
* 【処理2】デバイス管理マスタを取得する
* 【処理3】デバイス毎のハードウェア状態を取得する
* 【処理4】プリンタ制御プログラムログを取得する
* 【判断3】エラーログ有無
* 【処理5】バッチ処理ログを作成する
* 異常時は共通A処理を実行
*
* @return array 処理結果
*/
public function executeServerMonitoring(): array
{
$batchLogId = null;
$warnings = [];
$errorDetails = [];
try {
// バッチ処理開始ログ作成
$batchLog = BatchLog::createBatchLog(
'shj6',
BatchLog::STATUS_START,
[],
'SHJ-6 サーバ死活監視処理開始'
);
$batchLogId = $batchLog->id;
Log::info('SHJ-6 サーバ死活監視処理開始', [
'batch_log_id' => $batchLogId
]);
// 【処理1】サーバ死活監視DBアクセス
$dbAccessResult = $this->checkDatabaseAccess();
if (!$dbAccessResult['success']) {
// DB接続NGの場合は共通A処理実行
$this->executeCommonProcessA($batchLogId, 'DB接続エラー: ' . $dbAccessResult['message']);
return [
'success' => false,
'message' => 'データベース接続エラーが発生しました',
'error_details' => [$dbAccessResult['message']],
'batch_log_id' => $batchLogId
];
}
// 【処理2】デバイス管理マスタを取得する
$devices = $this->getDeviceManagementData();
Log::info('デバイス管理マスタ取得完了', [
'device_count' => count($devices)
]);
// 【処理3】デバイス毎のハードウェア状態を取得する
$hardwareStatusResult = $this->getHardwareStatus($devices);
if (!$hardwareStatusResult['success']) {
// ハードウェア状態取得できなかった場合は共通A処理実行
$this->executeCommonProcessA($batchLogId, 'ハードウェア状態取得エラー: ' . $hardwareStatusResult['message']);
$warnings[] = 'ハードウェア状態の一部で異常を検出しました';
$errorDetails[] = $hardwareStatusResult['message'];
}
// 【処理4】プリンタ制御プログラムログを取得する
$printerLogResult = $this->getPrinterControlLogs();
// 【判断3】エラーログ有無
if ($printerLogResult['has_errors']) {
// エラーログ有の場合は共通A処理実行
$this->executeCommonProcessA($batchLogId, 'プリンタエラーログ検出: ' . $printerLogResult['error_summary']);
$warnings[] = 'プリンタ制御でエラーが検出されました';
$errorDetails[] = $printerLogResult['error_summary'];
}
// 【処理5】バッチ処理ログを作成する
$monitoringSummary = $this->createMonitoringSummary($devices, $hardwareStatusResult, $printerLogResult);
$status = empty($warnings) ? BatchLog::STATUS_SUCCESS : BatchLog::STATUS_WARNING;
$message = empty($warnings) ?
'SHJ-6 サーバ死活監視処理正常完了' :
'SHJ-6 サーバ死活監視処理完了(警告あり)';
$batchLog->update([
'status' => $status,
'end_time' => now(),
'message' => $message,
'success_count' => 1
]);
Log::info('SHJ-6 サーバ死活監視処理完了', [
'batch_log_id' => $batchLogId,
'monitoring_summary' => $monitoringSummary,
'warnings' => $warnings
]);
return [
'success' => true,
'message' => 'SHJ-6 サーバ死活監視処理が完了しました',
'monitoring_summary' => $monitoringSummary,
'warnings' => $warnings,
'error_details' => $errorDetails,
'batch_log_id' => $batchLogId
];
} catch (\Exception $e) {
$errorMessage = 'SHJ-6 サーバ死活監視処理でエラーが発生: ' . $e->getMessage();
if (isset($batchLog) && $batchLog) {
$batchLog->update([
'status' => BatchLog::STATUS_ERROR,
'end_time' => now(),
'message' => $errorMessage,
'error_details' => $e->getMessage(),
'error_count' => 1
]);
}
// 例外発生時も共通A処理実行
$this->executeCommonProcessA($batchLogId, $errorMessage);
Log::error('SHJ-6 サーバ死活監視処理エラー', [
'batch_log_id' => $batchLogId,
'exception' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
return [
'success' => false,
'message' => $errorMessage,
'error_details' => [$e->getMessage()],
'batch_log_id' => $batchLogId
];
}
}
/**
* 【処理1】サーバ死活監視DBアクセス
*
* @return array アクセス結果
*/
private function checkDatabaseAccess(): array
{
try {
// 設定マスタテーブルへの簡単なクエリでDB接続確認
$result = DB::select('SELECT 1 as test');
if (empty($result)) {
return [
'success' => false,
'message' => 'データベースクエリの結果が空です'
];
}
Log::info('データベース接続確認成功');
return [
'success' => true,
'message' => 'データベース接続正常'
];
} catch (\Exception $e) {
Log::error('データベース接続エラー', [
'error' => $e->getMessage()
]);
return [
'success' => false,
'message' => $e->getMessage()
];
}
}
/**
* 【処理2】デバイス管理マスタを取得する
*
* @return array デバイス情報
*/
private function getDeviceManagementData(): array
{
try {
$devices = DB::table('device')
->select([
'device_id',
'park_id',
'device_type',
'device_subject',
'device_identifier',
'device_work',
'device_workstart',
'device_replace',
'device_remarks',
'operator_id'
])
->where('device_workstart', '<=', now())
->whereNull('device_replace')
->get()
->toArray();
Log::info('デバイス管理マスタ取得完了', [
'device_count' => count($devices)
]);
return $devices;
} catch (\Exception $e) {
Log::error('デバイス管理マスタ取得エラー', [
'error' => $e->getMessage()
]);
throw $e;
}
}
/**
* 【処理3】デバイス毎のハードウェア状態を取得する
*
* @param array $devices デバイス一覧
* @return array ハードウェア状態結果
*/
private function getHardwareStatus(array $devices): array
{
try {
$normalDevices = 0;
$abnormalDevices = 0;
$abnormalDetails = [];
foreach ($devices as $device) {
$latestStatus = HardwareCheckLog::getLatestStatusByDevice($device->device_id);
if (!$latestStatus) {
$abnormalDevices++;
$abnormalDetails[] = "デバイスID {$device->device_id}: ハードウェア状態ログが存在しません";
continue;
}
if ($latestStatus->isNormal()) {
$normalDevices++;
} else {
$abnormalDevices++;
$abnormalDetails[] = "デバイスID {$device->device_id}: {$latestStatus->getStatusNameAttribute()} - {$latestStatus->status_comment}";
}
}
Log::info('ハードウェア状態取得完了', [
'total_devices' => count($devices),
'normal_devices' => $normalDevices,
'abnormal_devices' => $abnormalDevices
]);
return [
'success' => $abnormalDevices === 0,
'total_devices' => count($devices),
'normal_devices' => $normalDevices,
'abnormal_devices' => $abnormalDevices,
'abnormal_details' => $abnormalDetails,
'message' => $abnormalDevices > 0 ?
"{$abnormalDevices}台のデバイスで異常を検出" :
'全デバイス正常'
];
} catch (\Exception $e) {
Log::error('ハードウェア状態取得エラー', [
'error' => $e->getMessage()
]);
return [
'success' => false,
'message' => $e->getMessage(),
'abnormal_details' => ["ハードウェア状態取得中にエラーが発生: " . $e->getMessage()]
];
}
}
/**
* 【処理4】プリンタ制御プログラムログを取得する
*
* @return array プリンタログ結果
*/
private function getPrinterControlLogs(): array
{
try {
// 過去15分間のエラーログを取得
$errorLogs = PrintJobLog::getRecentErrorLogs();
$hasErrors = $errorLogs->count() > 0;
$errorSummary = '';
$errorDetails = [];
if ($hasErrors) {
$errorsByCode = $errorLogs->groupBy('error_code');
$errorSummaryParts = [];
foreach ($errorsByCode as $errorCode => $logs) {
$count = $logs->count();
$errorSummaryParts[] = "エラーコード{$errorCode}: {$count}";
foreach ($logs as $log) {
$errorDetails[] = sprintf(
"[%s] %s - エラーコード: %d, %s",
$log->created_at->format('Y-m-d H:i:s'),
$log->process_name,
$log->error_code,
$log->status_comment
);
}
}
$errorSummary = implode(', ', $errorSummaryParts);
}
Log::info('プリンタ制御プログラムログ取得完了', [
'monitoring_period_minutes' => self::PRINTER_LOG_MONITOR_MINUTES,
'error_logs_count' => $errorLogs->count(),
'has_errors' => $hasErrors
]);
return [
'success' => true,
'has_errors' => $hasErrors,
'error_count' => $errorLogs->count(),
'error_summary' => $errorSummary,
'error_details' => $errorDetails,
'monitoring_period' => self::PRINTER_LOG_MONITOR_MINUTES . '分間'
];
} catch (\Exception $e) {
Log::error('プリンタ制御プログラムログ取得エラー', [
'error' => $e->getMessage()
]);
return [
'success' => false,
'has_errors' => true,
'error_summary' => 'ログ取得エラー: ' . $e->getMessage(),
'error_details' => ["プリンタログ取得中にエラーが発生: " . $e->getMessage()]
];
}
}
/**
* 監視結果サマリーを作成
*
* @param array $devices デバイス一覧
* @param array $hardwareResult ハードウェア結果
* @param array $printerResult プリンタ結果
* @return string 監視サマリー
*/
private function createMonitoringSummary(array $devices, array $hardwareResult, array $printerResult): string
{
$summary = [
'デバイス数: ' . count($devices),
'ハードウェア正常: ' . ($hardwareResult['normal_devices'] ?? 0) . '台',
'ハードウェア異常: ' . ($hardwareResult['abnormal_devices'] ?? 0) . '台',
'プリンタエラー: ' . ($printerResult['error_count'] ?? 0) . '件'
];
return implode(', ', $summary);
}
/**
* 共通A処理監視結果を反映
*
* @param int|null $batchLogId バッチログID
* @param string $alertMessage アラートメッセージ
* @return void
*/
private function executeCommonProcessA(?int $batchLogId, string $alertMessage): void
{
try {
Log::info('共通A処理開始', [
'batch_log_id' => $batchLogId,
'alert_message' => $alertMessage
]);
// 【共通判断1】DB反映可否判定
$canReflectToDb = $this->canReflectToDatabase();
if ($canReflectToDb) {
// 【共通処理1】オペレータキューを登録する
$this->registerOperatorQueue($alertMessage, $batchLogId);
// 【共通処理2】メール送信対象オペレータを取得する
$operators = $this->getMailTargetOperators();
// 【共通判断2】送信対象有無
if (!empty($operators)) {
foreach ($operators as $operator) {
$this->sendAlertMail($operator['email'], $alertMessage, 'オペレータ');
}
}
// 【共通処理3】駐輪場管理者を取得する
$parkManagers = $this->getParkManagers();
// 【共通判断3】送信対象有無
if (!empty($parkManagers)) {
foreach ($parkManagers as $manager) {
$this->sendAlertMail($manager['email'], $alertMessage, '駐輪場管理者');
}
}
} else {
// DB反映NGの場合は固定メールアドレスに送信
$this->sendAlertMail(self::FIXED_EMAIL_ADDRESS, $alertMessage, 'システム管理者');
}
Log::info('共通A処理完了', [
'batch_log_id' => $batchLogId
]);
} catch (\Exception $e) {
Log::error('共通A処理エラー', [
'batch_log_id' => $batchLogId,
'error' => $e->getMessage()
]);
}
}
/**
* DB反映可否判定
*
* @return bool 反映可能かどうか
*/
private function canReflectToDatabase(): bool
{
try {
// 簡単なINSERTテストでDB反映可否を確認
DB::beginTransaction();
$testId = DB::table('operator_que')->insertGetId([
'que_class' => 6,
'que_comment' => 'DB反映テスト',
'que_status' => 0,
'created_at' => now(),
'updated_at' => now()
]);
// テストレコードを削除
DB::table('operator_que')->where('que_id', $testId)->delete();
DB::commit();
return true;
} catch (\Exception $e) {
DB::rollBack();
Log::warning('DB反映不可', ['error' => $e->getMessage()]);
return false;
}
}
/**
* オペレータキューを登録
*
* @param string $alertMessage アラートメッセージ
* @param int|null $batchLogId バッチログID
* @return void
*/
private function registerOperatorQueue(string $alertMessage, ?int $batchLogId): void
{
try {
OperatorQue::create([
'que_class' => 6, // SHJ-6用のクラス
'user_id' => null,
'contract_id' => null,
'park_id' => null,
'que_comment' => $alertMessage,
'que_status' => 0, // 待機中
'que_status_comment' => 'システム監視アラート',
'work_instructions' => "SHJ-6監視処理 BatchLogID: {$batchLogId}",
'created_at' => now(),
'updated_at' => now()
]);
Log::info('オペレータキュー登録完了', [
'batch_log_id' => $batchLogId,
'alert_message' => $alertMessage
]);
} catch (\Exception $e) {
Log::error('オペレータキュー登録エラー', [
'batch_log_id' => $batchLogId,
'error' => $e->getMessage()
]);
}
}
/**
* メール送信対象オペレータを取得
*
* @return array オペレータ一覧
*/
private function getMailTargetOperators(): array
{
try {
// user_typeがオペレータのユーザーを取得仮の条件
$operators = DB::table('users')
->select(['user_id', 'email', 'name'])
->where('user_type', 'operator') // 実際のテーブル構造に合わせて調整
->whereNotNull('email')
->where('email', '!=', '')
->get()
->map(function ($user) {
return [
'user_id' => $user->user_id,
'email' => $user->email,
'name' => $user->name ?? 'オペレータ'
];
})
->toArray();
Log::info('メール送信対象オペレータ取得完了', [
'operator_count' => count($operators)
]);
return $operators;
} catch (\Exception $e) {
Log::error('メール送信対象オペレータ取得エラー', [
'error' => $e->getMessage()
]);
return [];
}
}
/**
* 駐輪場管理者を取得
*
* @return array 駐輪場管理者一覧
*/
private function getParkManagers(): array
{
try {
// 駐輪場管理者を取得(仮の条件)
$managers = DB::table('users')
->select(['user_id', 'email', 'name'])
->where('user_type', 'park_manager') // 実際のテーブル構造に合わせて調整
->whereNotNull('email')
->where('email', '!=', '')
->get()
->map(function ($user) {
return [
'user_id' => $user->user_id,
'email' => $user->email,
'name' => $user->name ?? '駐輪場管理者'
];
})
->toArray();
Log::info('駐輪場管理者取得完了', [
'manager_count' => count($managers)
]);
return $managers;
} catch (\Exception $e) {
Log::error('駐輪場管理者取得エラー', [
'error' => $e->getMessage()
]);
return [];
}
}
/**
* アラートメールを送信
*
* @param string $email メールアドレス
* @param string $alertMessage アラートメッセージ
* @param string $recipientType 受信者タイプ
* @return void
*/
private function sendAlertMail(string $email, string $alertMessage, string $recipientType): void
{
try {
// SHJメール送信機能を使用メールテンプレートID=1を使用、実際の値に調整
$result = $this->mailSendService->executeMailSend(
$email,
'', // 予備メールアドレスは空
1 // システムアラート用メールテンプレートID
);
if ($result['success']) {
Log::info('アラートメール送信成功', [
'email' => $email,
'recipient_type' => $recipientType,
'alert_message' => $alertMessage
]);
} else {
Log::error('アラートメール送信失敗', [
'email' => $email,
'recipient_type' => $recipientType,
'error' => $result['message']
]);
}
} catch (\Exception $e) {
Log::error('アラートメール送信エラー', [
'email' => $email,
'recipient_type' => $recipientType,
'error' => $e->getMessage()
]);
}
}
}

View File

@ -0,0 +1,583 @@
<?php
namespace App\Services;
use App\Models\Park;
use App\Models\RegularContract;
use App\Models\EarningsSummary;
use App\Models\Psection;
use App\Models\Batch\BatchLog;
use App\Models\OperatorQue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\DB;
use Carbon\Carbon;
/**
* SHJ-10 売上集計処理サービス
*
* 財政年度ベースの年次・月次売上集計処理を実行するビジネスロジック
* バッチ処理「SHJ-10売上集計」の核となる処理を担当
*/
class ShjTenService
{
/**
* Park モデル
*
* @var Park
*/
protected $parkModel;
/**
* RegularContract モデル
*
* @var RegularContract
*/
protected $contractModel;
/**
* EarningsSummary モデル
*
* @var EarningsSummary
*/
protected $earningsSummaryModel;
/**
* Psection モデル
*
* @var Psection
*/
protected $psectionModel;
/**
* BatchLog モデル
*
* @var BatchLog
*/
protected $batchLogModel;
/**
* OperatorQue モデル
*
* @var OperatorQue
*/
protected $operatorQueModel;
/**
* 財政年度開始月
*
* @var int
*/
const FISCAL_START_MONTH = 4;
/**
* コンストラクタ
*
* @param Park $parkModel
* @param RegularContract $contractModel
* @param EarningsSummary $earningsSummaryModel
* @param Psection $psectionModel
* @param BatchLog $batchLogModel
* @param OperatorQue $operatorQueModel
*/
public function __construct(
Park $parkModel,
RegularContract $contractModel,
EarningsSummary $earningsSummaryModel,
Psection $psectionModel,
BatchLog $batchLogModel,
OperatorQue $operatorQueModel
) {
$this->parkModel = $parkModel;
$this->contractModel = $contractModel;
$this->earningsSummaryModel = $earningsSummaryModel;
$this->psectionModel = $psectionModel;
$this->batchLogModel = $batchLogModel;
$this->operatorQueModel = $operatorQueModel;
}
/**
* SHJ-10 財政年度売上集計処理メイン実行
*
* 処理フロー:
* 【処理1】集計対象を設定する
* 【処理2】駐輪場マスタを取得する
* 【判断1】取得件数判定
* 【処理3】車種区分毎に算出する
* 【判断2】取得判定
* 【処理4】売上集計結果を削除→登録する
* 【処理5】オペレータキュー作成およびバッチ処理ログを作成する
*
* @param string $type 集計種別yearly/monthly
* @param string $target 集計対象
* @param array $fiscalPeriod 財政期間情報
* @return array 処理結果
*/
public function executeFiscalEarningsAggregation(string $type, string $target, array $fiscalPeriod): array
{
$batchLogId = null;
try {
// 【処理1】集計対象を設定する財政年度ベース
$aggregationTarget = $this->setFiscalAggregationTarget($fiscalPeriod);
// バッチ処理開始ログ作成
$batchLog = BatchLog::createBatchLog(
'shj10',
BatchLog::STATUS_START,
[
'type' => $type,
'target' => $target,
'fiscal_period' => $fiscalPeriod,
'aggregation_target' => $aggregationTarget
],
"SHJ-10 売上集計処理開始 ({$type}: {$fiscalPeriod['target_label']})"
);
$batchLogId = $batchLog->id;
Log::info('SHJ-10 売上集計処理開始', [
'batch_log_id' => $batchLogId,
'type' => $type,
'target' => $target,
'fiscal_period' => $fiscalPeriod,
'aggregation_target' => $aggregationTarget
]);
// 【処理2】駐輪場マスタを取得する
$parkInfo = $this->getParkInformation();
// 【判断1】取得件数判定
if (empty($parkInfo)) {
$typeLabel = $this->getTypeLabel($type);
$message = "売上集計({$typeLabel}):駐輪場マスタが存在していません。";
// バッチログ更新
$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->processFiscalEarningsForPark($park, $aggregationTarget, $fiscalPeriod);
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-10 売上集計処理正常完了 ({$type}: {$fiscalPeriod['target_label']}) - 駐輪場数: {$processedParks}, 集計レコード数: {$summaryRecords}";
$batchLog->update([
'status' => BatchLog::STATUS_SUCCESS,
'end_time' => now(),
'message' => $completionMessage,
'success_count' => 1
]);
// 【処理5】オペレータキュー作成
$this->createOperatorQueue($completionMessage, $batchLogId);
Log::info('SHJ-10 売上集計処理完了', [
'batch_log_id' => $batchLogId,
'processed_parks' => $processedParks,
'summary_records' => $summaryRecords
]);
return [
'success' => true,
'message' => 'SHJ-10 売上集計処理が正常に完了しました',
'processed_parks' => $processedParks,
'summary_records' => $summaryRecords,
'batch_log_id' => $batchLogId
];
} catch (\Exception $e) {
$errorMessage = 'SHJ-10 売上集計処理でエラーが発生: ' . $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-10 売上集計処理エラー', [
'batch_log_id' => $batchLogId,
'exception' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
return [
'success' => false,
'message' => $errorMessage,
'details' => $e->getMessage(),
'batch_log_id' => $batchLogId
];
}
}
/**
* 【処理1】財政年度集計対象を設定する
*
* @param array $fiscalPeriod 財政期間情報
* @return array 集計対象情報
*/
private function setFiscalAggregationTarget(array $fiscalPeriod): array
{
return [
'type' => $fiscalPeriod['type'],
'start_date' => $fiscalPeriod['start_date'],
'end_date' => $fiscalPeriod['end_date'],
'summary_type' => $fiscalPeriod['summary_type'],
'fiscal_year' => $fiscalPeriod['fiscal_year'],
'target_label' => $fiscalPeriod['target_label']
];
}
/**
* 【処理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 array $fiscalPeriod 財政期間情報
* @return int 作成された集計レコード数
*/
private function processFiscalEarningsForPark($park, array $aggregationTarget, array $fiscalPeriod): int
{
try {
// 【処理4】既存の売上集計結果を削除
$this->deleteExistingFiscalSummary($park->park_id, $aggregationTarget);
// 【処理3】車種区分毎に算出する
$psections = $this->getPsectionInformation();
$summaryRecords = 0;
foreach ($psections as $psection) {
$earningsData = $this->calculateFiscalEarningsForPsection(
$park->park_id,
$psection->psection_id,
$aggregationTarget
);
if ($this->hasEarningsData($earningsData)) {
// 売上集計結果を登録
$this->createFiscalEarningsSummary($park, $psection, $aggregationTarget, $earningsData, $fiscalPeriod);
$summaryRecords++;
}
}
Log::info('駐輪場財政年度売上集計完了', [
'park_id' => $park->park_id,
'park_name' => $park->park_name,
'summary_records' => $summaryRecords,
'fiscal_period' => $fiscalPeriod['target_label']
]);
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 calculateFiscalEarningsForPsection(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 deleteExistingFiscalSummary(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'])
->where('summary_type', $aggregationTarget['summary_type'])
->delete();
}
/**
* 財政年度売上集計結果を登録
*
* @param object $park 駐輪場情報
* @param object $psection 車種区分情報
* @param array $aggregationTarget 集計対象
* @param array $earningsData 売上データ
* @param array $fiscalPeriod 財政期間情報
* @return void
*/
private function createFiscalEarningsSummary($park, $psection, array $aggregationTarget, array $earningsData, array $fiscalPeriod): void
{
DB::table('earnings_summary')->insert([
'park_id' => $park->park_id,
'summary_type' => $aggregationTarget['summary_type'], // 1:年次, 2:月次
'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-10 {$fiscalPeriod['target_label']} 財政年度売上集計結果",
'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' => 10, // SHJ-10用のクラス
'user_id' => null,
'contract_id' => null,
'park_id' => null,
'que_comment' => $message,
'que_status' => 1, // 完了
'que_status_comment' => 'バッチ処理完了',
'work_instructions' => "SHJ-10売上集計処理 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 'yearly':
return '年次';
case 'monthly':
return '月次';
default:
return $type;
}
}
}

View File

@ -53,3 +53,5 @@ class UserService
}

View File

@ -26,3 +26,5 @@ class Csv
}

View File

@ -28,3 +28,5 @@ class Files
}

24
create_device_table.sql Normal file
View File

@ -0,0 +1,24 @@
--
-- SHJ-6 デバイス管理マスタテーブル
-- デバイス情報を管理するマスタテーブル
--
CREATE TABLE `device` (
`device_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'デバイスID(PK)',
`created_at` datetime DEFAULT NULL COMMENT '登録日時',
`updated_at` datetime DEFAULT NULL COMMENT '更新日時',
`park_id` int(10) UNSIGNED DEFAULT NULL COMMENT '駐輪場ID',
`device_type` varchar(255) DEFAULT NULL COMMENT 'デバイス種別',
`device_subject` varchar(255) DEFAULT NULL COMMENT 'デバイス名',
`device_identifier` varchar(255) DEFAULT NULL COMMENT '識別子(IP/FQDN)',
`device_work` varchar(255) DEFAULT NULL COMMENT '稼働(状態)',
`device_workstart` date DEFAULT NULL COMMENT '稼働開始日',
`device_replace` date DEFAULT NULL COMMENT 'リプレース予定日',
`device_remarks` varchar(255) DEFAULT NULL COMMENT '備考',
`operator_id` int(10) UNSIGNED DEFAULT NULL COMMENT '更新オペレータID',
PRIMARY KEY (`device_id`),
KEY `idx_park_id` (`park_id`),
KEY `idx_device_type` (`device_type`),
KEY `idx_workstart` (`device_workstart`),
KEY `idx_replace` (`device_replace`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='デバイス管理マスタテーブル';

View File

@ -0,0 +1,39 @@
--
-- SHJ-9 売上集計結果テーブル
-- 日次売上集計データを保存するテーブル
--
CREATE TABLE `earnings_summary` (
`earnings_summary_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '売上集計ID',
`park_id` int(10) UNSIGNED NOT NULL COMMENT '駐輪場ID',
`summary_type` varchar(255) DEFAULT NULL COMMENT '集計区分',
`summary_start_date` date DEFAULT NULL COMMENT '集計開始日',
`summary_end_date` date DEFAULT NULL COMMENT '集計終了日',
`earnings_date` date DEFAULT NULL COMMENT '売上日',
`psection_id` int(10) UNSIGNED DEFAULT NULL COMMENT '車種区分ID',
`usertype_subject1` varchar(255) DEFAULT NULL COMMENT '規格',
`enable_months` int(10) UNSIGNED DEFAULT NULL COMMENT '期間(月数)',
`regular_new_count` int(10) UNSIGNED DEFAULT 0 COMMENT '期間件数',
`regular_new_amount` decimal(10,2) DEFAULT 0.00 COMMENT '期間金額',
`regular_new_reduction_count` int(10) UNSIGNED DEFAULT 0 COMMENT '期間成免件数',
`regular_new_reduction_amount` decimal(10,2) DEFAULT 0.00 COMMENT '期間成免金額',
`regular_update_count` int(10) UNSIGNED DEFAULT 0 COMMENT '更新件数',
`regular_update_amount` decimal(10,2) DEFAULT 0.00 COMMENT '更新金額',
`regular_update_reduction_count` int(10) UNSIGNED DEFAULT 0 COMMENT '更新成免件数',
`regular_update_reduction_amount` decimal(10,2) DEFAULT 0.00 COMMENT '更新成免金額',
`turnsum_count` int(10) UNSIGNED DEFAULT 0 COMMENT '残金件数',
`turnsum` decimal(10,2) DEFAULT 0.00 COMMENT '残金',
`refunds` decimal(10,2) DEFAULT 0.00 COMMENT '解時返戻金',
`other_income` decimal(10,2) DEFAULT 0.00 COMMENT '分別収入',
`other_spending` decimal(10,2) DEFAULT 0.00 COMMENT '分別支出',
`reissue_count` int(10) UNSIGNED DEFAULT 0 COMMENT '発行件数',
`reissue_amount` decimal(10,2) DEFAULT 0.00 COMMENT '発行金額',
`summary_note` text DEFAULT NULL COMMENT '計備考',
`created_at` datetime DEFAULT NULL COMMENT '登録日時',
`updated_at` datetime DEFAULT NULL COMMENT '更新日時',
`operator_id` int(10) UNSIGNED DEFAULT NULL COMMENT '新法・ページID',
PRIMARY KEY (`earnings_summary_id`),
KEY `idx_park_earnings_date` (`park_id`, `earnings_date`),
KEY `idx_psection` (`psection_id`),
KEY `idx_earnings_date` (`earnings_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='売上集計結果テーブル';

View File

@ -0,0 +1,19 @@
--
-- SHJ-6 ハードウェアチェックログテーブル
-- デバイスのハードウェア状態監視ログを保存するテーブル
--
CREATE TABLE `hardware_check_log` (
`log_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'ハードウェアチェックログID(PK)',
`device_id` int(10) UNSIGNED NOT NULL COMMENT 'デバイスID',
`status` int(10) UNSIGNED DEFAULT NULL COMMENT 'ステータス',
`status_comment` varchar(255) DEFAULT NULL COMMENT 'ステータスコメント',
`created_at` datetime DEFAULT NULL COMMENT '登録日時',
`updated_at` datetime DEFAULT NULL COMMENT '更新日時',
`operator_id` int(10) UNSIGNED DEFAULT NULL COMMENT 'オペレータID',
PRIMARY KEY (`log_id`),
KEY `idx_device_id` (`device_id`),
KEY `idx_status` (`status`),
KEY `idx_created_at` (`created_at`),
KEY `idx_device_created` (`device_id`, `created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='ハードウェアチェックログテーブル';

View File

@ -0,0 +1,24 @@
--
-- SHJ-6 プリンタジョブログテーブル
-- プリンタ制御プログラムの実行ログを保存するテーブル
--
CREATE TABLE `print_job_log` (
`log_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '処理ログID(PK)',
`park_id` int(10) UNSIGNED DEFAULT NULL COMMENT '駐輪場ID',
`user_id` int(10) UNSIGNED DEFAULT NULL COMMENT 'ユーザーID',
`contract_id` int(10) UNSIGNED DEFAULT NULL COMMENT '契約ID',
`process_name` varchar(255) DEFAULT NULL COMMENT 'プロセス名',
`job_name` varchar(255) DEFAULT NULL COMMENT 'ジョブ名',
`status` varchar(255) DEFAULT NULL COMMENT 'ステータス',
`error_code` int(10) UNSIGNED DEFAULT NULL COMMENT 'エラーコード',
`status_comment` varchar(255) DEFAULT NULL COMMENT 'ステータスコメント',
`created_at` datetime DEFAULT NULL COMMENT '登録日時',
PRIMARY KEY (`log_id`),
KEY `idx_park_id` (`park_id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_contract_id` (`contract_id`),
KEY `idx_error_code` (`error_code`),
KEY `idx_created_at` (`created_at`),
KEY `idx_created_error` (`created_at`, `error_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='プリンタジョブログテーブル';

View File

@ -0,0 +1,62 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('earnings_summary', function (Blueprint $table) {
$table->id('earnings_summary_id')->comment('売上集計ID');
$table->unsignedInteger('park_id')->comment('駐輪場ID');
$table->string('summary_type', 255)->nullable()->comment('集計区分');
$table->date('summary_start_date')->nullable()->comment('集計開始日');
$table->date('summary_end_date')->nullable()->comment('集計終了日');
$table->date('earnings_date')->nullable()->comment('売上日');
$table->unsignedInteger('psection_id')->nullable()->comment('車種区分ID');
$table->string('usertype_subject1', 255)->nullable()->comment('規格');
$table->unsignedInteger('enable_months')->nullable()->comment('期間(月数)');
$table->unsignedInteger('regular_new_count')->nullable()->comment('期間件数');
$table->decimal('regular_new_amount', 10, 2)->nullable()->comment('期間金額');
$table->unsignedInteger('regular_new_reduction_count')->nullable()->comment('期間成免件数');
$table->decimal('regular_new_reduction_amount', 10, 2)->nullable()->comment('期間成免金額');
$table->unsignedInteger('regular_update_count')->nullable()->comment('更新件数');
$table->decimal('regular_update_amount', 10, 2)->nullable()->comment('更新金額');
$table->unsignedInteger('regular_update_reduction_count')->nullable()->comment('更新成免件数');
$table->decimal('regular_update_reduction_amount', 10, 2)->nullable()->comment('更新成免金額');
$table->unsignedInteger('turnsum_count')->nullable()->comment('残金件数');
$table->decimal('turnsum', 10, 2)->nullable()->comment('残金');
$table->decimal('refunds', 10, 2)->nullable()->comment('解時返戻金');
$table->decimal('other_income', 10, 2)->nullable()->comment('分別収入');
$table->decimal('other_spending', 10, 2)->nullable()->comment('分別支出');
$table->unsignedInteger('reissue_count')->nullable()->comment('発行件数');
$table->decimal('reissue_amount', 10, 2)->nullable()->comment('発行金額');
$table->text('summary_note')->nullable()->comment('計備考');
$table->datetime('created_at')->nullable()->comment('登録日時');
$table->datetime('updated_at')->nullable()->comment('更新日時');
$table->unsignedInteger('operator_id')->nullable()->comment('新法・ページID');
// インデックス
$table->index(['park_id', 'earnings_date'], 'idx_park_earnings_date');
$table->index(['psection_id'], 'idx_psection');
$table->index(['earnings_date'], 'idx_earnings_date');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('earnings_summary');
}
};