- 定期契約データを psectionusertypemonths で統合 - 新規/更新 減免/通常 を1レコードに集約 - Operator Queue に park_id と operator_id を正確に設定 - SQL に contract_money の SUM を追加
933 lines
37 KiB
PHP
933 lines
37 KiB
PHP
<?php
|
||
|
||
namespace App\Services;
|
||
|
||
use App\Models\Park;
|
||
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
|
||
{
|
||
/**
|
||
* 財政年度開始月
|
||
*
|
||
* @var int
|
||
*/
|
||
const FISCAL_START_MONTH = 4;
|
||
|
||
/**
|
||
* バッチ実行者の固定オペレータID
|
||
*/
|
||
const BATCH_OPERATOR_ID = 9999999;
|
||
|
||
/**
|
||
* 年次集計の summary_type 値
|
||
*/
|
||
const SUMMARY_TYPE_YEARLY = 1;
|
||
|
||
/**
|
||
* 月次集計の summary_type 値
|
||
*/
|
||
const SUMMARY_TYPE_MONTHLY = 2;
|
||
|
||
/**
|
||
* コンストラクタ
|
||
*/
|
||
public function __construct()
|
||
{
|
||
}
|
||
|
||
/**
|
||
* SHJ-10 財政年度売上集計処理メイン実行
|
||
*
|
||
* 処理フロー (todo/SHJ-10/SHJ-10.txt):
|
||
* 【処理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;
|
||
$batchLog = null;
|
||
$statusComments = []; // 内部変数.ステータスコメント
|
||
$dataIntegrityIssues = []; // 内部変数.情報不備
|
||
|
||
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);
|
||
$statusComment = "売上集計{$typeLabel}:駐輪場マスタが存在していません。";
|
||
$statusComments[] = $statusComment;
|
||
|
||
// バッチログ更新
|
||
$batchLog->update([
|
||
'status' => BatchLog::STATUS_WARNING,
|
||
'end_time' => now(),
|
||
'message' => $statusComment,
|
||
'success_count' => 1
|
||
]);
|
||
|
||
// 【処理5】オペレータキュー作成
|
||
$this->createOperatorQueue($statusComment, $batchLogId);
|
||
|
||
// SHJ-8 バッチ処理ログ作成
|
||
$this->createShjBatchLog([
|
||
'job_name' => 'SHJ-10売上集計(年次・月次)',
|
||
'status' => 'success',
|
||
'status_comment' => $statusComment
|
||
]);
|
||
|
||
return [
|
||
'success' => true,
|
||
'message' => $statusComment,
|
||
'processed_parks' => 0,
|
||
'summary_records' => 0,
|
||
'batch_log_id' => $batchLogId
|
||
];
|
||
}
|
||
|
||
// 【処理3】車種区分毎に算出する & 【処理4】売上集計結果を削除→登録する
|
||
$summaryRecords = 0;
|
||
$processedParks = 0;
|
||
|
||
foreach ($parkInfo as $park) {
|
||
$result = $this->processFiscalEarningsForPark($park, $aggregationTarget, $fiscalPeriod, $batchLogId);
|
||
|
||
$processedParks++;
|
||
$summaryRecords += $result['summary_records'];
|
||
|
||
// 対象データなしの場合のステータスコメント収集
|
||
if (!empty($result['no_data_message'])) {
|
||
$statusComments[] = $result['no_data_message'];
|
||
}
|
||
|
||
// 情報不備を収集("なし"でない場合)
|
||
if ($result['data_integrity_issue'] !== '情報不備:なし') {
|
||
$dataIntegrityIssues[] = $result['data_integrity_issue'];
|
||
}
|
||
}
|
||
|
||
// 最終ステータスコメント生成
|
||
$finalStatusComment = "SHJ-10 売上集計処理正常完了 ({$type}: {$fiscalPeriod['target_label']}) - 駐輪場数: {$processedParks}, 集計レコード数: {$summaryRecords}";
|
||
if (!empty($statusComments)) {
|
||
$finalStatusComment .= "\n" . implode("\n", $statusComments);
|
||
}
|
||
if (!empty($dataIntegrityIssues)) {
|
||
$finalStatusComment .= "\n" . implode("\n", $dataIntegrityIssues);
|
||
}
|
||
|
||
// バッチ処理完了ログ更新
|
||
$batchLog->update([
|
||
'status' => BatchLog::STATUS_SUCCESS,
|
||
'end_time' => now(),
|
||
'message' => $finalStatusComment,
|
||
'success_count' => 1
|
||
]);
|
||
|
||
// 【処理5】オペレータキュー作成
|
||
// ※ 駐輪場単位で既に作成済み(processFiscalEarningsForPark内で情報不備検出時に実施)
|
||
if (!empty($dataIntegrityIssues)) {
|
||
Log::warning('SHJ-10 情報不備検出', [
|
||
'batch_log_id' => $batchLogId,
|
||
'issues' => $dataIntegrityIssues
|
||
]);
|
||
}
|
||
|
||
// SHJ-8 バッチ処理ログ作成
|
||
$this->createShjBatchLog([
|
||
'job_name' => 'SHJ-10売上集計(年次・月次)',
|
||
'status' => 'success',
|
||
'status_comment' => $finalStatusComment
|
||
]);
|
||
|
||
Log::info('SHJ-10 売上集計処理完了', [
|
||
'batch_log_id' => $batchLogId,
|
||
'processed_parks' => $processedParks,
|
||
'summary_records' => $summaryRecords,
|
||
'data_integrity_issues' => count($dataIntegrityIssues),
|
||
'no_data_parks' => count($statusComments)
|
||
]);
|
||
|
||
return [
|
||
'success' => true,
|
||
'message' => 'SHJ-10 売上集計処理が正常に完了しました',
|
||
'processed_parks' => $processedParks,
|
||
'summary_records' => $summaryRecords,
|
||
'data_integrity_issues' => count($dataIntegrityIssues),
|
||
'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
|
||
]);
|
||
}
|
||
|
||
// SHJ-8 バッチ処理ログ作成(エラー時も作成)
|
||
try {
|
||
$this->createShjBatchLog([
|
||
'job_name' => 'SHJ-10売上集計(年次・月次)',
|
||
'status' => 'error',
|
||
'status_comment' => $errorMessage
|
||
]);
|
||
} catch (\Exception $shjException) {
|
||
Log::error('SHJ-8呼び出しエラー', ['error' => $shjException->getMessage()]);
|
||
}
|
||
|
||
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】駐輪場マスタを取得する
|
||
*
|
||
* @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 財政期間情報
|
||
* @param int $batchLogId バッチログID
|
||
* @return array 処理結果 ['summary_records' => int, 'data_integrity_issue' => string, 'no_data_message' => string|null]
|
||
*/
|
||
private function processFiscalEarningsForPark($park, array $aggregationTarget, array $fiscalPeriod, int $batchLogId): array
|
||
{
|
||
try {
|
||
$startDate = $aggregationTarget['start_date'];
|
||
$endDate = $aggregationTarget['end_date'];
|
||
|
||
// 0. 情報不備チェック
|
||
$dataIntegrityIssue = $this->checkDataIntegrity($park->park_id, $startDate, $endDate);
|
||
|
||
// 情報不備がある場合、駐輪場単位でオペレータキュー作成(仕様 todo/SHJ-10/SHJ-10.txt:289-299)
|
||
if ($dataIntegrityIssue !== '情報不備:なし') {
|
||
$this->createOperatorQueue($dataIntegrityIssue, $batchLogId, $park->park_id);
|
||
}
|
||
|
||
// ① 定期契約データ取得(車種区分・分類名1・定期有効月数毎)
|
||
$regularData = $this->calculateRegularEarnings($park->park_id, $startDate, $endDate);
|
||
|
||
// ② 一時金データ取得(車種毎)
|
||
$lumpsumData = $this->calculateLumpsumEarnings($park->park_id, $startDate, $endDate);
|
||
|
||
// ③ 解約返戻金データ取得(車種区分毎)
|
||
$refundData = $this->calculateRefundEarnings($park->park_id, $startDate, $endDate);
|
||
|
||
// ④ 再発行データ取得(車種区分毎)
|
||
$reissueData = $this->calculateReissueCount($park->park_id, $startDate, $endDate);
|
||
|
||
// 【判断2】データがいずれかあれば【処理4】へ
|
||
if (empty($regularData) && empty($lumpsumData) && empty($refundData) && empty($reissueData)) {
|
||
// 対象データなし - 仕様 todo/SHJ-10/SHJ-10.txt:209-211
|
||
$typeLabel = $this->getTypeLabel($aggregationTarget['type']);
|
||
$noDataMessage = "売上集計{$typeLabel}:{$startDate}~{$endDate}/駐輪場:{$park->park_name}:売上データが存在しません。";
|
||
|
||
return [
|
||
'summary_records' => 0,
|
||
'data_integrity_issue' => $dataIntegrityIssue,
|
||
'no_data_message' => $noDataMessage
|
||
];
|
||
}
|
||
|
||
// 【処理4】既存の売上集計結果を削除
|
||
$this->deleteExistingFiscalSummary($park->park_id, $aggregationTarget);
|
||
|
||
// 【処理4】売上集計結果を登録
|
||
$summaryRecords = 0;
|
||
|
||
// ① 定期契約データがある場合:同じ組合せ(psection×usertype×months)を統合
|
||
$mergedRegularData = $this->mergeRegularDataByGroup($regularData);
|
||
foreach ($mergedRegularData as $key => $mergedRow) {
|
||
$this->createFiscalEarningsSummary($park, $mergedRow, $aggregationTarget, $fiscalPeriod, 'regular');
|
||
$summaryRecords++;
|
||
}
|
||
|
||
// ②③④ 一時金・解約・再発行データがある場合(車種区分毎に集約)
|
||
$otherDataByPsection = $this->mergeOtherEarningsData($lumpsumData, $refundData, $reissueData);
|
||
foreach ($otherDataByPsection as $psectionId => $data) {
|
||
$this->createFiscalEarningsSummary($park, $data, $aggregationTarget, $fiscalPeriod, 'other');
|
||
$summaryRecords++;
|
||
}
|
||
|
||
Log::info('駐輪場売上集計完了', [
|
||
'park_id' => $park->park_id,
|
||
'park_name' => $park->park_name,
|
||
'summary_records' => $summaryRecords,
|
||
'data_integrity_issue' => $dataIntegrityIssue
|
||
]);
|
||
|
||
return [
|
||
'summary_records' => $summaryRecords,
|
||
'data_integrity_issue' => $dataIntegrityIssue,
|
||
'no_data_message' => null
|
||
];
|
||
|
||
} catch (\Exception $e) {
|
||
Log::error('駐輪場売上集計エラー', [
|
||
'park_id' => $park->park_id,
|
||
'error' => $e->getMessage()
|
||
]);
|
||
|
||
throw $e;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 0. 情報不備チェック(期間集計版)
|
||
*
|
||
* 仕様 todo/SHJ-10/SHJ-10.txt:77-101
|
||
*
|
||
* @param int $parkId 駐輪場ID
|
||
* @param string $startDate 集計開始日(YYYY-MM-DD)
|
||
* @param string $endDate 集計終了日(YYYY-MM-DD)
|
||
* @return string 情報不備メッセージ(仕様フォーマット:"情報不備:xxx" or "情報不備:なし")
|
||
*/
|
||
private function checkDataIntegrity(int $parkId, string $startDate, string $endDate): string
|
||
{
|
||
$incompleteContracts = DB::table('regular_contract')
|
||
->select('contract_id')
|
||
->where('park_id', $parkId)
|
||
->where('contract_flag', 1)
|
||
->whereBetween(DB::raw('DATE(contract_payment_day)'), [$startDate, $endDate])
|
||
->where(function($query) {
|
||
$query->whereNull('update_flag')
|
||
->orWhereNull('psection_id')
|
||
->orWhereNull('enable_months');
|
||
})
|
||
->pluck('contract_id')
|
||
->toArray();
|
||
|
||
if (empty($incompleteContracts)) {
|
||
return '情報不備:なし';
|
||
}
|
||
|
||
// 仕様フォーマット:"情報不備:" + 契約IDカンマ区切り
|
||
return '情報不備:' . implode(',', $incompleteContracts);
|
||
}
|
||
|
||
/**
|
||
* ① 定期契約データ取得(車種区分・分類名1・定期有効月数毎)
|
||
* ※ 金額・件数は全て0固定だが、分類や月数の組み合わせごとにレコードが必要
|
||
*
|
||
* 仕様 todo/SHJ-10/SHJ-10.txt:104-128
|
||
*
|
||
* @param int $parkId 駐輪場ID
|
||
* @param string $startDate 集計開始日(YYYY-MM-DD)
|
||
* @param string $endDate 集計終了日(YYYY-MM-DD)
|
||
* @return array
|
||
*/
|
||
/**
|
||
* ① 定期契約データ取得(車種区分・分類名1・定期有効月数毎)
|
||
*
|
||
* 仕様 todo/SHJ-10/SHJ-10.txt:104-129
|
||
* SQL定義:減免措置・継続フラグ・車種区分・分類名・有効月数でグループ化し、
|
||
* 授受金額の合計と件数を算出する
|
||
*
|
||
* @param int $parkId 駐輪場ID
|
||
* @param string $startDate 集計開始日(YYYY-MM-DD)
|
||
* @param string $endDate 集計終了日(YYYY-MM-DD)
|
||
* @return array
|
||
*/
|
||
private function calculateRegularEarnings(int $parkId, string $startDate, string $endDate): array
|
||
{
|
||
$results = DB::table('regular_contract as T1')
|
||
->join('usertype as T3', 'T1.user_categoryid', '=', 'T3.user_categoryid')
|
||
->select([
|
||
DB::raw('IFNULL(T1.contract_reduction, 0) as contract_reduction'),
|
||
'T1.update_flag',
|
||
'T1.psection_id',
|
||
'T3.usertype_subject1',
|
||
'T1.enable_months',
|
||
DB::raw('SUM(T1.contract_money) as total_amount'), // 仕様:授受金額の合計
|
||
DB::raw('COUNT(T1.contract_money) as contract_count') // 仕様:授受件数
|
||
])
|
||
->where('T1.park_id', $parkId)
|
||
->where('T1.contract_flag', 1)
|
||
->whereBetween(DB::raw('DATE(T1.contract_payment_day)'), [$startDate, $endDate])
|
||
->whereNotNull('T1.update_flag')
|
||
->whereNotNull('T1.psection_id')
|
||
->whereNotNull('T1.enable_months')
|
||
->groupBy([
|
||
DB::raw('IFNULL(T1.contract_reduction, 0)'),
|
||
'T1.update_flag',
|
||
'T1.psection_id',
|
||
'T3.usertype_subject1',
|
||
'T1.enable_months'
|
||
])
|
||
->get();
|
||
|
||
return $results->toArray();
|
||
}
|
||
|
||
/**
|
||
* ② 一時金データ取得(車種毎)
|
||
*
|
||
* 仕様 todo/SHJ-10/SHJ-10.txt:148-159
|
||
*
|
||
* @param int $parkId 駐輪場ID
|
||
* @param string $startDate 集計開始日(YYYY-MM-DD)
|
||
* @param string $endDate 集計終了日(YYYY-MM-DD)
|
||
* @return array
|
||
*/
|
||
private function calculateLumpsumEarnings(int $parkId, string $startDate, string $endDate): array
|
||
{
|
||
$results = DB::table('lumpsum_transaction')
|
||
->select([
|
||
'type_class as psection_id',
|
||
DB::raw('COUNT(*) as lumpsum_count'),
|
||
DB::raw('COALESCE(SUM(deposit_amount), 0) as lumpsum')
|
||
])
|
||
->where('park_id', $parkId)
|
||
->whereBetween(DB::raw('DATE(pay_date)'), [$startDate, $endDate])
|
||
->groupBy('type_class')
|
||
->get();
|
||
|
||
return $results->toArray();
|
||
}
|
||
|
||
/**
|
||
* ③ 解約返戻金データ取得(車種区分毎)
|
||
*
|
||
* 仕様 todo/SHJ-10/SHJ-10.txt:160-171
|
||
*
|
||
* @param int $parkId 駐輪場ID
|
||
* @param string $startDate 集計開始日(YYYY-MM-DD)
|
||
* @param string $endDate 集計終了日(YYYY-MM-DD)
|
||
* @return array
|
||
*/
|
||
private function calculateRefundEarnings(int $parkId, string $startDate, string $endDate): array
|
||
{
|
||
$results = DB::table('regular_contract')
|
||
->select([
|
||
'psection_id',
|
||
DB::raw('COALESCE(SUM(refunds), 0) as refunds')
|
||
])
|
||
->where('park_id', $parkId)
|
||
->where('contract_cancel_flag', 1)
|
||
->whereBetween(DB::raw('DATE(repayment_at)'), [$startDate, $endDate])
|
||
->groupBy('psection_id')
|
||
->get();
|
||
|
||
return $results->toArray();
|
||
}
|
||
|
||
/**
|
||
* ④ 再発行データ取得(車種区分毎)
|
||
*
|
||
* 仕様 todo/SHJ-10/SHJ-10.txt:172-183
|
||
*
|
||
* @param int $parkId 駐輪場ID
|
||
* @param string $startDate 集計開始日(YYYY-MM-DD)
|
||
* @param string $endDate 集計終了日(YYYY-MM-DD)
|
||
* @return array
|
||
*/
|
||
private function calculateReissueCount(int $parkId, string $startDate, string $endDate): array
|
||
{
|
||
$results = DB::table('seal')
|
||
->select([
|
||
'psection_id',
|
||
DB::raw('COUNT(contract_id) as reissue_count')
|
||
])
|
||
->where('park_id', $parkId)
|
||
->where('contract_seal_issue', '>=', 2)
|
||
->whereBetween(DB::raw('DATE(seal_day)'), [$startDate, $endDate])
|
||
->groupBy('psection_id')
|
||
->get();
|
||
|
||
return $results->toArray();
|
||
}
|
||
|
||
/**
|
||
* 定期契約データを組合せ毎に統合
|
||
*
|
||
* SQLは contract_reduction × update_flag で分組しているため、
|
||
* 同じ psection × usertype × months の組合せで複数行が返る場合がある。
|
||
* ここで park_id + psection_id + usertype_subject1 + enable_months をキーに統合し、
|
||
* 新規/更新 × 減免/通常 の各件数・金額を1つのオブジェクトに集約する。
|
||
*
|
||
* 仕様 todo/SHJ-10/SHJ-10.txt:130-147
|
||
*
|
||
* @param array $regularData calculateRegularEarnings()の結果
|
||
* @return array キー:psection_id|usertype|months、値:統合されたデータオブジェクト
|
||
*/
|
||
private function mergeRegularDataByGroup(array $regularData): array
|
||
{
|
||
$merged = [];
|
||
|
||
foreach ($regularData as $row) {
|
||
// 統合キー:psection_id|usertype_subject1|enable_months
|
||
$key = $row->psection_id . '|' . $row->usertype_subject1 . '|' . $row->enable_months;
|
||
|
||
// 初回作成
|
||
if (!isset($merged[$key])) {
|
||
$merged[$key] = (object)[
|
||
'psection_id' => $row->psection_id,
|
||
'usertype_subject1' => $row->usertype_subject1,
|
||
'enable_months' => $row->enable_months,
|
||
// 新規・通常
|
||
'regular_new_count' => 0,
|
||
'regular_new_amount' => 0,
|
||
// 新規・減免
|
||
'regular_new_reduction_count' => 0,
|
||
'regular_new_reduction_amount' => 0,
|
||
// 更新・通常
|
||
'regular_update_count' => 0,
|
||
'regular_update_amount' => 0,
|
||
// 更新・減免
|
||
'regular_update_reduction_count' => 0,
|
||
'regular_update_reduction_amount' => 0
|
||
];
|
||
}
|
||
|
||
// 区分判定
|
||
$isNew = in_array($row->update_flag, [2, null]); // 新規
|
||
$isReduction = ($row->contract_reduction == 1); // 減免
|
||
|
||
$count = $row->contract_count ?? 0;
|
||
$amount = $row->total_amount ?? 0;
|
||
|
||
// 対応するフィールドに累加
|
||
if ($isNew && !$isReduction) {
|
||
// 新規・通常
|
||
$merged[$key]->regular_new_count += $count;
|
||
$merged[$key]->regular_new_amount += $amount;
|
||
} elseif ($isNew && $isReduction) {
|
||
// 新規・減免
|
||
$merged[$key]->regular_new_reduction_count += $count;
|
||
$merged[$key]->regular_new_reduction_amount += $amount;
|
||
} elseif (!$isNew && !$isReduction) {
|
||
// 更新・通常
|
||
$merged[$key]->regular_update_count += $count;
|
||
$merged[$key]->regular_update_amount += $amount;
|
||
} elseif (!$isNew && $isReduction) {
|
||
// 更新・減免
|
||
$merged[$key]->regular_update_reduction_count += $count;
|
||
$merged[$key]->regular_update_reduction_amount += $amount;
|
||
}
|
||
}
|
||
|
||
return $merged;
|
||
}
|
||
|
||
/**
|
||
* 一時金・解約・再発行データを車種区分毎に統合
|
||
*
|
||
* @param array $lumpsumData
|
||
* @param array $refundData
|
||
* @param array $reissueData
|
||
* @return array
|
||
*/
|
||
private function mergeOtherEarningsData(array $lumpsumData, array $refundData, array $reissueData): array
|
||
{
|
||
$merged = [];
|
||
|
||
// 一時金
|
||
foreach ($lumpsumData as $row) {
|
||
$psectionId = $row->psection_id;
|
||
if (!isset($merged[$psectionId])) {
|
||
$merged[$psectionId] = (object)[
|
||
'psection_id' => $psectionId,
|
||
'usertype_subject1' => null,
|
||
'enable_months' => 0,
|
||
'lumpsum_count' => 0,
|
||
'lumpsum' => 0,
|
||
'refunds' => 0,
|
||
'reissue_count' => 0
|
||
];
|
||
}
|
||
$merged[$psectionId]->lumpsum_count = $row->lumpsum_count;
|
||
$merged[$psectionId]->lumpsum = $row->lumpsum;
|
||
}
|
||
|
||
// 解約返戻金
|
||
foreach ($refundData as $row) {
|
||
$psectionId = $row->psection_id;
|
||
if (!isset($merged[$psectionId])) {
|
||
$merged[$psectionId] = (object)[
|
||
'psection_id' => $psectionId,
|
||
'usertype_subject1' => null,
|
||
'enable_months' => 0,
|
||
'lumpsum_count' => 0,
|
||
'lumpsum' => 0,
|
||
'refunds' => 0,
|
||
'reissue_count' => 0
|
||
];
|
||
}
|
||
$merged[$psectionId]->refunds = $row->refunds;
|
||
}
|
||
|
||
// 再発行
|
||
foreach ($reissueData as $row) {
|
||
$psectionId = $row->psection_id;
|
||
if (!isset($merged[$psectionId])) {
|
||
$merged[$psectionId] = (object)[
|
||
'psection_id' => $psectionId,
|
||
'usertype_subject1' => null,
|
||
'enable_months' => 0,
|
||
'lumpsum_count' => 0,
|
||
'lumpsum' => 0,
|
||
'refunds' => 0,
|
||
'reissue_count' => 0
|
||
];
|
||
}
|
||
$merged[$psectionId]->reissue_count = $row->reissue_count;
|
||
}
|
||
|
||
return $merged;
|
||
}
|
||
|
||
/**
|
||
* 【処理4】既存の売上集計結果を削除
|
||
*
|
||
* 仕様書のキー:駐輪場ID, 集計種別, 集計開始日, 集計終了日, 売上日付, 車種区分, 分類名1, 定期有効月数
|
||
* 仕様 todo/SHJ-10/SHJ-10.txt:213-221
|
||
* 仕様書どおり summary_start_date と summary_end_date は NULL で保存されているため、NULL で検索する
|
||
*
|
||
* @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_type', $aggregationTarget['summary_type'])
|
||
->whereNull('summary_start_date') // 仕様書line 251: null
|
||
->whereNull('summary_end_date') // 仕様書line 252: null
|
||
->where('earnings_date', $aggregationTarget['end_date'])
|
||
// psection_id, usertype_subject1, enable_months は
|
||
// レコードごとに異なるため、ここでは指定しない
|
||
->delete();
|
||
|
||
Log::debug('既存の売上集計結果削除', [
|
||
'park_id' => $parkId,
|
||
'summary_type' => $aggregationTarget['summary_type'],
|
||
'earnings_date' => $aggregationTarget['end_date']
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* 売上集計結果を登録(財政年度ベース)
|
||
*
|
||
* 仕様 todo/SHJ-10/SHJ-10.txt:215-284
|
||
*
|
||
* @param object $park 駐輪場情報
|
||
* @param object $data 売上データ
|
||
* @param array $aggregationTarget 集計対象
|
||
* @param array $fiscalPeriod 財政期間情報
|
||
* @param string $dataType データ種別(regular or other)
|
||
* @return void
|
||
*/
|
||
private function createFiscalEarningsSummary($park, $data, array $aggregationTarget, array $fiscalPeriod, string $dataType): void
|
||
{
|
||
$insertData = [
|
||
'park_id' => $park->park_id,
|
||
'summary_type' => $aggregationTarget['summary_type'], // 1=年次, 2=月次
|
||
'summary_start_date' => null, // 仕様書line 251: null
|
||
'summary_end_date' => null, // 仕様書line 252: null
|
||
'earnings_date' => $aggregationTarget['end_date'], // 集計終了日
|
||
'psection_id' => $data->psection_id,
|
||
'usertype_subject1' => $data->usertype_subject1 ?? null, // 実際の分類名
|
||
'enable_months' => $data->enable_months ?? 0, // 実際の定期有効月数
|
||
'summary_note' => "SHJ-10:{$fiscalPeriod['target_label']}/{$aggregationTarget['start_date']}~{$aggregationTarget['end_date']}", // 仕様line 272
|
||
'created_at' => now(),
|
||
'updated_at' => now(),
|
||
'operator_id' => self::BATCH_OPERATOR_ID // 9999999 (仕様line 275)
|
||
];
|
||
|
||
if ($dataType === 'regular') {
|
||
// 定期契約データの場合:mergeRegularDataByGroup()で既に統合済み
|
||
// 新規/更新 × 減免/通常 の各件数・金額がすべて含まれている (仕様line 130-147)
|
||
$insertData = array_merge($insertData, [
|
||
'regular_new_count' => $data->regular_new_count ?? 0,
|
||
'regular_new_amount' => $data->regular_new_amount ?? 0,
|
||
'regular_new_reduction_count' => $data->regular_new_reduction_count ?? 0,
|
||
'regular_new_reduction_amount' => $data->regular_new_reduction_amount ?? 0,
|
||
'regular_update_count' => $data->regular_update_count ?? 0,
|
||
'regular_update_amount' => $data->regular_update_amount ?? 0,
|
||
'regular_update_reduction_count' => $data->regular_update_reduction_count ?? 0,
|
||
'regular_update_reduction_amount' => $data->regular_update_reduction_amount ?? 0,
|
||
'lumpsum_count' => 0, // 仕様line 140
|
||
'lumpsum' => 0, // 仕様line 141
|
||
'refunds' => 0, // 仕様line 142
|
||
'other_income' => 0, // 仕様line 143
|
||
'other_spending' => 0, // 仕様line 144
|
||
'reissue_count' => 0, // 仕様line 145
|
||
'reissue_amount' => 0 // 仕様line 146
|
||
]);
|
||
} else {
|
||
// 一時金・解約・再発行データの場合:定期フィールドは0固定 (仕様line 186-202)
|
||
$insertData = array_merge($insertData, [
|
||
'regular_new_count' => 0, // 仕様line 186
|
||
'regular_new_amount' => 0, // 仕様line 187
|
||
'regular_new_reduction_count' => 0, // 仕様line 188
|
||
'regular_new_reduction_amount' => 0, // 仕様line 189
|
||
'regular_update_count' => 0, // 仕様line 190
|
||
'regular_update_amount' => 0, // 仕様line 191
|
||
'regular_update_reduction_count' => 0, // 仕様line 192
|
||
'regular_update_reduction_amount' => 0, // 仕様line 193
|
||
'lumpsum_count' => $data->lumpsum_count ?? 0, // 仕様line 194
|
||
'lumpsum' => $data->lumpsum ?? 0, // 仕様line 195
|
||
'refunds' => $data->refunds ?? 0, // 仕様line 196
|
||
'other_income' => 0, // 仕様line 197
|
||
'other_spending' => 0, // 仕様line 198
|
||
'reissue_count' => $data->reissue_count ?? 0, // 仕様line 199
|
||
'reissue_amount' => 0 // 仕様line 200: 0固定
|
||
]);
|
||
}
|
||
|
||
DB::table('earnings_summary')->insert($insertData);
|
||
|
||
Log::debug('売上集計結果登録', [
|
||
'park_id' => $park->park_id,
|
||
'psection_id' => $data->psection_id,
|
||
'data_type' => $dataType,
|
||
'summary_type' => $aggregationTarget['summary_type']
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* 【処理5】オペレータキュー作成(駐輪場単位・情報不備がある場合のみ)
|
||
*
|
||
* 仕様 todo/SHJ-10/SHJ-10.txt:289-317
|
||
* - que_class: 14(集計対象エラー)
|
||
* - que_comment: 空(仕様書line 297)
|
||
* - que_status: 1(キュー発生)
|
||
* - que_status_comment: 空(仕様書line 299)
|
||
* - work_instructions: 情報不備メッセージ(仕様書line 300)
|
||
* - park_id: 駐輪場ID(仕様 "処理1.駐輪場ID"、パラメータエラー時はnull)
|
||
* - operator_id: 9999999(バッチ処理固定値)
|
||
*
|
||
* @param string $message 情報不備メッセージ
|
||
* @param int $batchLogId バッチログID
|
||
* @param int|null $parkId 駐輪場ID(パラメータエラー時はnull)
|
||
* @return void
|
||
*/
|
||
private function createOperatorQueue(string $message, int $batchLogId, ?int $parkId = null): void
|
||
{
|
||
try {
|
||
DB::table('operator_que')->insert([
|
||
'que_class' => 14, // 集計対象エラー
|
||
'user_id' => null,
|
||
'contract_id' => null,
|
||
'park_id' => $parkId, // 仕様:処理1.駐輪場ID
|
||
'que_comment' => '', // 仕様: 空
|
||
'que_status' => 1, // キュー発生
|
||
'que_status_comment' => '', // 仕様: 空
|
||
'work_instructions' => $message, // 仕様: 情報不備
|
||
'operator_id' => self::BATCH_OPERATOR_ID, // 9999999
|
||
'created_at' => now(),
|
||
'updated_at' => now()
|
||
]);
|
||
|
||
Log::info('オペレータキュー作成完了', [
|
||
'batch_log_id' => $batchLogId,
|
||
'park_id' => $parkId,
|
||
'que_class' => 14,
|
||
'que_status' => 1,
|
||
'operator_id' => self::BATCH_OPERATOR_ID,
|
||
'work_instructions' => $message
|
||
]);
|
||
|
||
} catch (\Exception $e) {
|
||
Log::error('オペレータキュー作成エラー', [
|
||
'batch_log_id' => $batchLogId,
|
||
'park_id' => $parkId,
|
||
'error' => $e->getMessage()
|
||
]);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* SHJ-8 バッチ処理ログ作成
|
||
*
|
||
* 共通処理「SHJ-8 バッチ処理ログ作成」を呼び出す
|
||
*
|
||
* @param array $statistics 処理統計情報
|
||
* @return void
|
||
*/
|
||
private function createShjBatchLog(array $statistics): void
|
||
{
|
||
try {
|
||
// SHJ-8 パラメータ設定
|
||
$deviceId = 9999999; // バッチ処理用固定デバイスID
|
||
$processName = 'SHJ-10';
|
||
$jobName = $statistics['job_name'];
|
||
$status = $statistics['status'];
|
||
$statusComment = $statistics['status_comment'] ?? '';
|
||
|
||
$createdDate = now()->format('Y/m/d');
|
||
$updatedDate = now()->format('Y/m/d');
|
||
|
||
Log::info('SHJ-8 バッチ処理ログ作成', [
|
||
'device_id' => $deviceId,
|
||
'process_name' => $processName,
|
||
'job_name' => $jobName,
|
||
'status' => $status,
|
||
'status_comment' => $statusComment
|
||
]);
|
||
|
||
// 共通処理 SHJ-8 バッチ処理ログ作成を呼び出し
|
||
// BatchLog システムを使用してバッチ処理の実行ログを記録
|
||
BatchLog::createBatchLog(
|
||
$processName,
|
||
$status,
|
||
[
|
||
'device_id' => $deviceId,
|
||
'job_name' => $jobName,
|
||
'status_comment' => $statusComment,
|
||
'statistics' => $statistics,
|
||
'shj8_params' => [
|
||
'device_id' => $deviceId,
|
||
'process_name' => $processName,
|
||
'job_name' => $jobName,
|
||
'status' => $status,
|
||
'created_date' => $createdDate,
|
||
'updated_date' => $updatedDate
|
||
]
|
||
],
|
||
$statusComment
|
||
);
|
||
|
||
} catch (\Exception $e) {
|
||
Log::error('SHJ-8 バッチ処理ログ作成エラー', [
|
||
'error' => $e->getMessage(),
|
||
'statistics' => $statistics
|
||
]);
|
||
|
||
// SHJ-8でエラーが発生してもメイン処理は継続
|
||
// エラーログのみ出力
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 集計種別のラベル取得
|
||
*
|
||
* @param string $type 集計種別
|
||
* @return string ラベル
|
||
*/
|
||
private function getTypeLabel(string $type): string
|
||
{
|
||
switch ($type) {
|
||
case 'yearly':
|
||
return '(年次)';
|
||
case 'monthly':
|
||
return '(月次)';
|
||
default:
|
||
return '';
|
||
}
|
||
}
|
||
}
|