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

584 lines
20 KiB
PHP
Raw 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-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;
}
}
}