so-manager-dev.com/app/Services/ShjTenService.php
Your Name 9441a34f6f SHJ-9/SHJ-10: 修复定期契約集計処理の統合ロジック
- 定期契約データを psectionusertypemonths で統合
- 新規/更新  減免/通常 を1レコードに集約
- Operator Queue に park_id と operator_id を正確に設定
- SQL に contract_money の SUM を追加
2025-10-03 20:32:09 +09:00

933 lines
37 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\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 '';
}
}
}