so-manager-dev.com/app/Services/ShjNineService.php
2025-09-19 19:01:21 +09:00

596 lines
20 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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;
}
}
}