888 lines
34 KiB
PHP
888 lines
34 KiB
PHP
<?php
|
||
|
||
namespace App\Services;
|
||
|
||
use App\Models\Park;
|
||
use App\Models\EarningsSummary;
|
||
use App\Models\Psection;
|
||
use App\Models\OperatorQue;
|
||
use Illuminate\Support\Facades\Log;
|
||
use Illuminate\Support\Facades\DB;
|
||
use Carbon\Carbon;
|
||
|
||
/**
|
||
* SHJ-9 売上集計処理サービス
|
||
*
|
||
* 日次の売上集計処理を実行するビジネスロジック
|
||
* バッチ処理「SHJ-9売上集計(日次)」の核となる処理を担当
|
||
*/
|
||
class ShjNineService
|
||
{
|
||
/**
|
||
* バッチ実行者の固定オペレータID
|
||
*/
|
||
const BATCH_OPERATOR_ID = 9999999;
|
||
|
||
/**
|
||
* 日次集計の summary_type 値
|
||
*/
|
||
const SUMMARY_TYPE_DAILY = 3;
|
||
|
||
/**
|
||
* 情報不備なしの固定文字列(全角スペース付き)
|
||
*/
|
||
const DATA_INTEGRITY_NONE = ' 情報不備:なし';
|
||
|
||
/**
|
||
* ShjEightService インスタンス
|
||
*
|
||
* @var ShjEightService
|
||
*/
|
||
protected $shjEightService;
|
||
|
||
/**
|
||
* コンストラクタ
|
||
*
|
||
* @param ShjEightService $shjEightService
|
||
*/
|
||
public function __construct(ShjEightService $shjEightService)
|
||
{
|
||
$this->shjEightService = $shjEightService;
|
||
}
|
||
|
||
/**
|
||
* SHJ-9 売上集計処理メイン実行(日次のみ)
|
||
*
|
||
* 処理フロー:
|
||
* 【処理1】集計対象を設定する
|
||
* 【処理2】駐輪場マスタを取得する
|
||
* 【判断1】取得件数判定
|
||
* 【処理3】車種区分毎に算出する
|
||
* 【判断2】取得判定
|
||
* 【処理4】売上集計結果を削除→登録する
|
||
* 【処理5】オペレータキュー作成およびバッチ処理ログを作成する
|
||
*
|
||
* @param string $type 集計種別(daily 固定)
|
||
* @param string $aggregationDate 集計対象日(YYYY-MM-DD)
|
||
* @return array 処理結果
|
||
*/
|
||
public function executeEarningsAggregation(string $type, string $aggregationDate): array
|
||
{
|
||
$statusComments = []; // 内部変数.ステータスコメント
|
||
$allDataIntegrity = []; // 内部変数.情報不備(なし含む全件)
|
||
$dataIntegrityIssues = []; // オペレータキュー作成用(NULL以外)
|
||
|
||
try {
|
||
// 【処理1】集計対象を設定する
|
||
// パラメーター検証(日付形式チェック)
|
||
if (!$this->isValidDateFormat($aggregationDate)) {
|
||
// 日付形式エラー時は【処理5】へ(仕様JOB1:ステータスコメント設定→JOB5)
|
||
$statusComment = "売上集計(日次):パラメーターが不正です。(日付形式ではありません)";
|
||
|
||
// 仕様JOB5:情報不備がNULL→オペレータキュー作成しない(日付エラーは情報不備ではない)
|
||
|
||
// SHJ-8 バッチ処理ログ作成(仕様JOB5:ステータスは常にsuccess)
|
||
$shjEightResult = $this->callShjEight('SHJ-9売上集計(日次)', 'success', $statusComment);
|
||
$this->evaluateShjEightResult($shjEightResult);
|
||
|
||
return [
|
||
'success' => true, // 仕様上はwarningで成功扱い
|
||
'message' => $statusComment,
|
||
'processed_parks' => 0,
|
||
'summary_records' => 0
|
||
];
|
||
}
|
||
|
||
$targetDate = Carbon::parse($aggregationDate)->format('Y-m-d');
|
||
|
||
Log::info('SHJ-9 売上集計処理開始', [
|
||
'type' => $type,
|
||
'target_date' => $targetDate
|
||
]);
|
||
|
||
// 【処理2】駐輪場マスタを取得する
|
||
$parkInfo = $this->getParkInformation();
|
||
|
||
// 【判断1】取得件数判定
|
||
if (empty($parkInfo)) {
|
||
$statusComment = '売上集計(日次):駐輪場マスタが存在していません。';
|
||
|
||
// 仕様JOB2-STEP1:情報不備がNULL→オペレータキュー作成しない
|
||
|
||
// SHJ-8 バッチ処理ログ作成(仕様JOB5:ステータスは常にsuccess)
|
||
$shjEightResult = $this->callShjEight('SHJ-9売上集計(日次)', 'success', $statusComment);
|
||
$this->evaluateShjEightResult($shjEightResult);
|
||
|
||
return [
|
||
'success' => true,
|
||
'message' => $statusComment,
|
||
'processed_parks' => 0,
|
||
'summary_records' => 0
|
||
];
|
||
}
|
||
|
||
// 仕様JOB3/JOB4:車種区分毎に算出→売上集計結果を登録
|
||
$summaryRecords = 0;
|
||
$processedParks = 0;
|
||
|
||
foreach ($parkInfo as $park) {
|
||
$result = $this->processEarningsForPark($park, $targetDate);
|
||
|
||
$processedParks++;
|
||
$summaryRecords += $result['summary_records'];
|
||
|
||
// 仕様JOB4:レコード毎のステータスコメントを収集
|
||
if (!empty($result['status_comments'])) {
|
||
$statusComments = array_merge($statusComments, $result['status_comments']);
|
||
}
|
||
|
||
// 仕様JOB3-STEP1:対象データなしの場合のステータスコメント
|
||
if (!empty($result['no_data_message'])) {
|
||
$statusComments[] = $result['no_data_message'];
|
||
}
|
||
|
||
// 仕様JOB5:全parkの情報不備を収集(なし含む)
|
||
$allDataIntegrity[] = $result['data_integrity_issue'];
|
||
|
||
// 仕様JOB5:NULL以外の場合にオペレータキューを登録
|
||
if ($result['data_integrity_issue'] !== null) {
|
||
$dataIntegrityIssues[] = [
|
||
'park_id' => $park->park_id,
|
||
'message' => $result['data_integrity_issue']
|
||
];
|
||
}
|
||
}
|
||
|
||
// 仕様JOB5:情報不備がNULL以外の場合、オペレータキューを登録
|
||
foreach ($dataIntegrityIssues as $issue) {
|
||
$this->createOperatorQueue($issue['message'], $issue['park_id']);
|
||
}
|
||
|
||
if (!empty($dataIntegrityIssues)) {
|
||
Log::warning('SHJ-9 情報不備検出', [
|
||
'issues' => $dataIntegrityIssues
|
||
]);
|
||
}
|
||
|
||
// 仕様JOB5:ステータスコメント = 内部変数.ステータスコメント + 内部変数.情報不備(なし含む全件)
|
||
$allParts = array_merge($statusComments, $allDataIntegrity);
|
||
$finalStatusComment = implode("\n", $allParts);
|
||
|
||
Log::info('SHJ-9 完全ステータスコメント', ['status_comment' => $finalStatusComment]);
|
||
|
||
// 仕様JOB5:SHJ-8 バッチ処理ログ作成(ステータスは常にsuccess)
|
||
$shjEightResult = $this->callShjEight('SHJ-9売上集計(日次)', 'success', $finalStatusComment);
|
||
$this->evaluateShjEightResult($shjEightResult);
|
||
|
||
Log::info('SHJ-9 売上集計処理完了', [
|
||
'processed_parks' => $processedParks,
|
||
'summary_records' => $summaryRecords,
|
||
'data_integrity_issues' => count($dataIntegrityIssues)
|
||
]);
|
||
|
||
return [
|
||
'success' => true,
|
||
'message' => 'SHJ-9 売上集計処理が正常に完了しました',
|
||
'processed_parks' => $processedParks,
|
||
'summary_records' => $summaryRecords,
|
||
'data_integrity_issues' => count($dataIntegrityIssues)
|
||
];
|
||
|
||
} catch (\Exception $e) {
|
||
$errorMessage = '売上集計(日次):エラー発生 - ' . $e->getMessage();
|
||
|
||
// SHJ-8 バッチ処理ログ作成(エラー時も作成)
|
||
try {
|
||
$shjEightResult = $this->callShjEight('SHJ-9売上集計(日次)', 'success', $errorMessage);
|
||
$this->evaluateShjEightResult($shjEightResult);
|
||
} catch (\Exception $shjException) {
|
||
Log::error('SHJ-8呼び出しエラー', ['error' => $shjException->getMessage()]);
|
||
}
|
||
|
||
Log::error('SHJ-9 売上集計処理エラー', [
|
||
'exception' => $e->getMessage(),
|
||
'trace' => $e->getTraceAsString()
|
||
]);
|
||
|
||
return [
|
||
'success' => false,
|
||
'message' => $errorMessage,
|
||
'details' => $e->getMessage()
|
||
];
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 日付形式の検証(厳格:YYYY-MM-DD形式のみ許可)
|
||
*
|
||
* @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]);
|
||
}
|
||
|
||
/**
|
||
* 【処理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 string $targetDate 集計対象日(YYYY-MM-DD)
|
||
* @return array 処理結果
|
||
*/
|
||
private function processEarningsForPark($park, string $targetDate): array
|
||
{
|
||
try {
|
||
// 仕様JOB3-0:情報不備チェック(SQL-2)
|
||
$dataIntegrityIssue = $this->checkDataIntegrity($park->park_id, $targetDate);
|
||
|
||
// 仕様JOB3-①:定期契約データ取得(車種区分・分類名1・定期有効月数毎)
|
||
$regularData = $this->calculateRegularEarnings($park->park_id, $targetDate);
|
||
|
||
// 仕様JOB3-②:一時金データ取得(車種毎)
|
||
$lumpsumData = $this->calculateLumpsumEarnings($park->park_id, $targetDate);
|
||
|
||
// 仕様JOB3-③:解約返戻金データ取得(車種区分毎)
|
||
$refundData = $this->calculateRefundEarnings($park->park_id, $targetDate);
|
||
|
||
// 仕様JOB3-④:再発行データ取得(車種区分毎)
|
||
$reissueData = $this->calculateReissueCount($park->park_id, $targetDate);
|
||
|
||
// 仕様JOB3-STEP1:①②③④のデータがいずれかあれば→JOB4へ
|
||
if (empty($regularData) && empty($lumpsumData) && empty($refundData) && empty($reissueData)) {
|
||
// 仕様JOB3-STEP1:対象データなし
|
||
$noDataMessage = "売上集計(日次):対象日:{$targetDate}/駐輪場:{$park->park_name}:売上データが存在しません。";
|
||
|
||
return [
|
||
'summary_records' => 0,
|
||
'data_integrity_issue' => $dataIntegrityIssue,
|
||
'no_data_message' => $noDataMessage,
|
||
'status_comments' => []
|
||
];
|
||
}
|
||
|
||
// 仕様JOB4:売上集計結果を登録(同一キーの既存レコードは各insert前に削除)
|
||
$summaryRecords = 0;
|
||
$statusComments = [];
|
||
|
||
// ① 定期契約データ:同じ組合せ(psection×usertype×months)を統合
|
||
$mergedRegularData = $this->mergeRegularDataByGroup($regularData);
|
||
foreach ($mergedRegularData as $key => $mergedRow) {
|
||
$sc = $this->createEarningsSummary($park, $mergedRow, $targetDate, 'regular', $dataIntegrityIssue);
|
||
$statusComments[] = $sc;
|
||
$summaryRecords++;
|
||
}
|
||
|
||
// ②③④ 一時金・解約・再発行データ(車種区分毎に集約)
|
||
$otherDataByPsection = $this->mergeOtherEarningsData($lumpsumData, $refundData, $reissueData);
|
||
foreach ($otherDataByPsection as $psectionId => $data) {
|
||
$sc = $this->createEarningsSummary($park, $data, $targetDate, 'other', null);
|
||
$statusComments[] = $sc;
|
||
$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,
|
||
'status_comments' => $statusComments
|
||
];
|
||
|
||
} catch (\Exception $e) {
|
||
Log::error('駐輪場売上集計エラー', [
|
||
'park_id' => $park->park_id,
|
||
'error' => $e->getMessage()
|
||
]);
|
||
|
||
throw $e;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 0. 情報不備チェック
|
||
*
|
||
* @param int $parkId 駐輪場ID
|
||
* @param string $targetDate 集計対象日(YYYY-MM-DD)
|
||
* @return string 情報不備メッセージ(仕様フォーマット:"情報不備:xxx" or "情報不備:なし")
|
||
*/
|
||
private function checkDataIntegrity(int $parkId, string $targetDate): string
|
||
{
|
||
// 仕様SQL-2:情報不備チェック(ログ吐き出し)
|
||
$incompleteContracts = DB::table('regular_contract')
|
||
->select(['contract_id', 'contract_reduction', 'update_flag', 'psection_id', 'enable_months'])
|
||
->where('park_id', $parkId)
|
||
->where('contract_flag', 1)
|
||
->whereDate('contract_payment_day', '=', $targetDate)
|
||
->where(function($query) {
|
||
$query->whereNull('update_flag')
|
||
->orWhereNull('psection_id')
|
||
->orWhereNull('enable_months');
|
||
})
|
||
->get()
|
||
->toArray();
|
||
|
||
if (empty($incompleteContracts)) {
|
||
return self::DATA_INTEGRITY_NONE;
|
||
}
|
||
|
||
// 仕様SQL-2:ログ吐き出し(詳細情報を出力)
|
||
Log::warning('SHJ-9 情報不備契約詳細', [
|
||
'park_id' => $parkId,
|
||
'target_date' => $targetDate,
|
||
'contracts' => $incompleteContracts
|
||
]);
|
||
|
||
// 仕様フォーマット:" 情報不備:" + 契約IDカンマ区切り(全角スペース付き)
|
||
$contractIds = array_map(function($c) {
|
||
return is_object($c) ? $c->contract_id : $c['contract_id'];
|
||
}, $incompleteContracts);
|
||
|
||
return ' 情報不備:' . implode(',', $contractIds);
|
||
}
|
||
|
||
/**
|
||
* ① 定期契約データ取得(車種区分・分類名1・定期有効月数毎)
|
||
*
|
||
* SQL定義:減免措置・継続フラグ・車種区分・分類名・有効月数でグループ化し、
|
||
* 授受金額の合計と件数を算出する
|
||
*
|
||
* @param int $parkId 駐輪場ID
|
||
* @param string $targetDate 集計対象日(YYYY-MM-DD)
|
||
* @return array
|
||
*/
|
||
private function calculateRegularEarnings(int $parkId, string $targetDate): 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'), // 仕様line 77: 授受金額の合計
|
||
DB::raw('COUNT(T1.contract_money) as contract_count') // 仕様line 78: 授受件数
|
||
])
|
||
->where('T1.park_id', $parkId)
|
||
->where('T1.contract_flag', 1)
|
||
->whereDate('T1.contract_payment_day', '=', $targetDate)
|
||
->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();
|
||
}
|
||
|
||
/**
|
||
* ② 一時金データ取得(車種毎)
|
||
*
|
||
* @param int $parkId 駐輪場ID
|
||
* @param string $targetDate 集計対象日(YYYY-MM-DD)
|
||
* @return array
|
||
*/
|
||
private function calculateLumpsumEarnings(int $parkId, string $targetDate): 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)
|
||
->whereDate('pay_date', '=', $targetDate)
|
||
->groupBy('type_class')
|
||
->get();
|
||
|
||
return $results->toArray();
|
||
}
|
||
|
||
/**
|
||
* ③ 解約返戻金データ取得(車種区分毎)
|
||
*
|
||
* @param int $parkId 駐輪場ID
|
||
* @param string $targetDate 集計対象日(YYYY-MM-DD)
|
||
* @return array
|
||
*/
|
||
private function calculateRefundEarnings(int $parkId, string $targetDate): 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)
|
||
->whereDate('repayment_at', '=', $targetDate)
|
||
->groupBy('psection_id')
|
||
->get();
|
||
|
||
return $results->toArray();
|
||
}
|
||
|
||
/**
|
||
* ④ 再発行データ取得(車種区分毎)
|
||
*
|
||
* @param int $parkId 駐輪場ID
|
||
* @param string $targetDate 集計対象日(YYYY-MM-DD)
|
||
* @return array
|
||
*/
|
||
private function calculateReissueCount(int $parkId, string $targetDate): 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)
|
||
->whereDate('seal_day', '=', $targetDate)
|
||
->groupBy('psection_id')
|
||
->get();
|
||
|
||
return $results->toArray();
|
||
}
|
||
|
||
/**
|
||
* 定期契約データを組合せ毎に統合
|
||
*
|
||
* SQLは contract_reduction × update_flag で分組しているため、
|
||
* 同じ psection × usertype × months の組合せで複数行が返る場合がある。
|
||
* ここで park_id + psection_id + usertype_subject1 + enable_months をキーに統合し、
|
||
* 新規/更新 × 減免/通常 の各件数・金額を1つのオブジェクトに集約する。
|
||
*
|
||
* @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;
|
||
}
|
||
|
||
/**
|
||
* 車種区分名を取得
|
||
*
|
||
* @param int|null $psectionId 車種区分ID
|
||
* @return string 車種区分名
|
||
*/
|
||
private function getPsectionName(?int $psectionId): string
|
||
{
|
||
if ($psectionId === null) {
|
||
return '';
|
||
}
|
||
|
||
$name = DB::table('psection')
|
||
->where('psection_id', $psectionId)
|
||
->value('psection_subject');
|
||
|
||
return $name ?? '';
|
||
}
|
||
|
||
/**
|
||
* 仕様JOB4:売上集計結果を削除→登録
|
||
*
|
||
* 同一キー(駐車場ID,集計種別,集計開始日,集計終了日,売上日付,車種区分,分類名1,定期有効月数)
|
||
* が既に登録済みの場合削除した上で、新規レコードを登録する。
|
||
*
|
||
* @param object $park 駐輪場情報
|
||
* @param object $data 売上データ
|
||
* @param string $targetDate 集計対象日(YYYY-MM-DD)
|
||
* @param string $dataType データ種別(regular or other)
|
||
* @param string|null $dataIntegrityIssue 情報不備メッセージ(regularのみ使用)
|
||
* @return string 仕様JOB4形式のステータスコメント
|
||
*/
|
||
private function createEarningsSummary($park, $data, string $targetDate, string $dataType, ?string $dataIntegrityIssue = null): string
|
||
{
|
||
$usertypeSubject1 = $data->usertype_subject1 ?? null;
|
||
$enableMonths = $data->enable_months ?? 0;
|
||
|
||
// 仕様JOB4:同一キーの既存レコードを削除
|
||
$deleteQuery = DB::table('earnings_summary')
|
||
->where('park_id', $park->park_id)
|
||
->where('summary_type', self::SUMMARY_TYPE_DAILY)
|
||
->whereNull('summary_start_date')
|
||
->whereNull('summary_end_date')
|
||
->where('earnings_date', $targetDate);
|
||
|
||
// NULL値対応:psection_idがnullの場合はwhereNull
|
||
if ($data->psection_id === null) {
|
||
$deleteQuery->whereNull('psection_id');
|
||
} else {
|
||
$deleteQuery->where('psection_id', $data->psection_id);
|
||
}
|
||
|
||
// NULL値対応:usertype_subject1がnullの場合はwhereNull
|
||
if ($usertypeSubject1 === null) {
|
||
$deleteQuery->whereNull('usertype_subject1');
|
||
} else {
|
||
$deleteQuery->where('usertype_subject1', $usertypeSubject1);
|
||
}
|
||
$deleteQuery->where('enable_months', $enableMonths);
|
||
$deleteQuery->delete();
|
||
|
||
// 仕様:集計備考の生成
|
||
if ($dataType === 'regular' && $dataIntegrityIssue !== null) {
|
||
// 仕様JOB3-①:定期データのsummary_noteには情報不備を含める
|
||
$summaryNote = "SHJ-9:{$targetDate}{$dataIntegrityIssue}";
|
||
} else {
|
||
// 仕様JOB3-②③④:otherデータのsummary_note
|
||
$summaryNote = "SHJ-9:{$targetDate}";
|
||
}
|
||
|
||
$insertData = [
|
||
'park_id' => $park->park_id,
|
||
'summary_type' => self::SUMMARY_TYPE_DAILY,
|
||
'summary_start_date' => null,
|
||
'summary_end_date' => null,
|
||
'earnings_date' => $targetDate,
|
||
'psection_id' => $data->psection_id,
|
||
'usertype_subject1' => $usertypeSubject1,
|
||
'enable_months' => $enableMonths,
|
||
'summary_note' => $summaryNote,
|
||
'created_at' => now(),
|
||
'updated_at' => now(),
|
||
'operator_id' => self::BATCH_OPERATOR_ID
|
||
];
|
||
|
||
if ($dataType === 'regular') {
|
||
// 仕様JOB3-①:定期契約データ
|
||
$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,
|
||
'lumpsum' => 0,
|
||
'refunds' => 0,
|
||
'other_income' => 0,
|
||
'other_spending' => 0,
|
||
'reissue_count' => 0,
|
||
'reissue_amount' => 0
|
||
]);
|
||
} else {
|
||
// 仕様JOB3-②③④:一時金・解約・再発行データ(定期フィールドは0固定)
|
||
$insertData = array_merge($insertData, [
|
||
'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,
|
||
'lumpsum_count' => $data->lumpsum_count ?? 0,
|
||
'lumpsum' => $data->lumpsum ?? 0,
|
||
'refunds' => $data->refunds ?? 0,
|
||
'other_income' => 0,
|
||
'other_spending' => 0,
|
||
'reissue_count' => $data->reissue_count ?? 0,
|
||
'reissue_amount' => 0
|
||
]);
|
||
}
|
||
|
||
DB::table('earnings_summary')->insert($insertData);
|
||
|
||
// 仕様JOB4:レコード毎のステータスコメント生成(全データ型共通フォーマット)
|
||
$psectionName = $this->getPsectionName($data->psection_id);
|
||
$displayUsertype = $usertypeSubject1 ?? '';
|
||
$statusComment = "売上集計(日次):対象日:{$targetDate}/駐輪場:{$park->park_name}/車種区分:{$psectionName}/分類名1:{$displayUsertype}/定期有効月数:{$enableMonths}";
|
||
|
||
Log::debug('売上集計結果登録', [
|
||
'park_id' => $park->park_id,
|
||
'psection_id' => $data->psection_id,
|
||
'data_type' => $dataType,
|
||
'target_date' => $targetDate
|
||
]);
|
||
|
||
return $statusComment;
|
||
}
|
||
|
||
/**
|
||
* 【処理5】オペレータキュー作成(駐輪場単位・情報不備がある場合のみ)
|
||
*
|
||
* - que_class: 14(集計対象エラー)
|
||
* - que_comment: 空文字("")
|
||
* - que_status: 1(キュー発生)
|
||
* - que_status_comment: 空文字("")
|
||
* - work_instructions: 情報不備メッセージ
|
||
* - park_id: 駐輪場ID(仕様 "処理1.駐輪場ID"、パラメータエラー時はnull)
|
||
* - operator_id: 9999999(バッチ処理固定値)
|
||
*
|
||
* @param string $message 情報不備メッセージ
|
||
* @param int|null $parkId 駐輪場ID(パラメータエラー時はnull)
|
||
* @return void
|
||
*/
|
||
private function createOperatorQueue(string $message, ?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' => '', // 仕様line 260: ""
|
||
'que_status' => 1, // キュー発生
|
||
'que_status_comment' => '', // 仕様line 262: ""
|
||
'work_instructions' => $message, // 仕様line 263: 情報不備
|
||
'operator_id' => self::BATCH_OPERATOR_ID, // 9999999
|
||
'created_at' => now(),
|
||
'updated_at' => now()
|
||
]);
|
||
|
||
Log::info('オペレータキュー作成完了', [
|
||
'park_id' => $parkId,
|
||
'que_class' => 14,
|
||
'que_status' => 1,
|
||
'operator_id' => self::BATCH_OPERATOR_ID,
|
||
'work_instructions' => $message
|
||
]);
|
||
|
||
} catch (\Exception $e) {
|
||
Log::error('オペレータキュー作成エラー', [
|
||
'park_id' => $parkId,
|
||
'error' => $e->getMessage()
|
||
]);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* SHJ-8 バッチ処理ログ作成
|
||
*
|
||
* 共通処理「SHJ-8 バッチ処理ログ作成」を呼び出す
|
||
*
|
||
* @param string $jobName ジョブ名
|
||
* @param string $status ステータス (success/error)
|
||
* @param string $statusComment 業務固有のステータスコメント
|
||
* @return array SHJ-8実行結果
|
||
*/
|
||
private function callShjEight(string $jobName, string $status, string $statusComment): array
|
||
{
|
||
try {
|
||
// 仕様書: SHJ-9パラメーター仕様 デバイスID = (空)
|
||
$today = now()->format('Y/m/d');
|
||
|
||
$result = $this->shjEightService->execute(
|
||
null,
|
||
'SHJ-9',
|
||
$jobName,
|
||
$status,
|
||
$statusComment,
|
||
$today,
|
||
$today
|
||
);
|
||
|
||
Log::info('SHJ-8 バッチ処理ログ作成完了', [
|
||
'job_name' => $jobName,
|
||
'status' => $status,
|
||
'shj8_result' => $result['result'] ?? null
|
||
]);
|
||
|
||
return $result;
|
||
|
||
} catch (\Exception $e) {
|
||
Log::error('SHJ-8 バッチ処理ログ作成エラー', [
|
||
'error' => $e->getMessage(),
|
||
'job_name' => $jobName,
|
||
'status_comment' => $statusComment
|
||
]);
|
||
throw $e;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* SHJ-8実行結果の判定
|
||
*
|
||
* 仕様JOB5の判定(処理結果=0/その他)をログで明示する。
|
||
*
|
||
* @param array $shjEightResult SHJ-8返却値
|
||
* @return void
|
||
*/
|
||
private function evaluateShjEightResult(array $shjEightResult): void
|
||
{
|
||
if ((int)($shjEightResult['result'] ?? 1) === 0) {
|
||
Log::info('SHJ-9 SHJ-8処理結果判定: 0(正常)');
|
||
return;
|
||
}
|
||
|
||
Log::warning('SHJ-9 SHJ-8処理結果判定: 0以外(異常)', [
|
||
'shj8_result' => $shjEightResult
|
||
]);
|
||
}
|
||
}
|