api.so-manager-dev.com/app/Services/ShjFourBService.php
Your Name 41814dd908
All checks were successful
Deploy api / deploy (push) Successful in 23s
SHJ-4 SHJ-5 SHJ-6 変更点実装
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 19:25:40 +09:00

1374 lines
55 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\SettlementTransaction;
use App\Models\RegularContract;
use App\Models\Park;
use App\Models\PriceA;
use App\Models\BatJobLog;
use App\Models\Device;
use App\Models\User;
use App\Services\ShjFourCService;
use App\Services\ShjThirteenService;
use App\Services\ShjEightService;
use App\Services\ShjMailSendService;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Carbon\Carbon;
/**
* SHJ-4B 定期契約更新処理サービス
*
* SHJ-4Aで登録された決済情報を基に定期契約の更新処理を実行する
* ウェルネットのPUSH通知を契機とした決済トランザクション処理
*/
class ShjFourBService
{
/**
* 金額比較結果の定数
*/
const AMOUNT_MATCH = 'match'; // 金額一致
const AMOUNT_SHORTAGE = 'shortage'; // 授受過少
const AMOUNT_EXCESS = 'excess'; // 授受超過
/**
* 契約フラグの状態定数
*/
const CONTRACT_FLAG_PROCESSING = 0; // 処理中
const CONTRACT_FLAG_UPDATED = 1; // 更新済
const CONTRACT_FLAG_ERROR = 2; // エラー状態
/**
* SHJ-8 バッチ処理ログ作成サービス
*
* @var ShjEightService
*/
protected $shjEightService;
/**
* SHJ-4C 車室割り当てサービス
*
* @var ShjFourCService
*/
protected $shjFourCService;
/**
* SHJ-7 メール送信サービス
*
* @var ShjMailSendService
*/
protected $mailSendService;
/**
* コンストラクタ
*
* @param ShjEightService $shjEightService
* @param ShjMailSendService $mailSendService
* @param ShjFourCService $shjFourCService
*/
public function __construct(
ShjEightService $shjEightService,
ShjMailSendService $mailSendService,
ShjFourCService $shjFourCService
) {
$this->shjEightService = $shjEightService;
$this->mailSendService = $mailSendService;
$this->shjFourCService = $shjFourCService;
}
/**
* 決済トランザクション処理メイン実行
*
* SHJ-4Bの3段階判断処理を実装
* 【判断0】対象契約取得判定
* 【判断1】授受状態チェック
* 【判断2】金額チェック
*
* @param int $settlementTransactionId 決済トランザクションID
* @param array $context 追加のコンテキスト情報
* @return array 処理結果
*/
public function processSettlementTransaction(int $settlementTransactionId, array $context = []): array
{
$startTime = now();
Log::info('SHJ-4B 決済トランザクション処理開始', [
'settlement_transaction_id' => $settlementTransactionId,
'context' => $context,
'start_time' => $startTime,
]);
try {
// 【前処理】決済トランザクション取得
$settlement = $this->getSettlementTransaction($settlementTransactionId);
// 【処理1】定期契約マスタの対象レコード取得
// 【判断0】取得判定登録済み判定を含む
$contractResult = $this->judgeTargetContract($settlement);
if (!$contractResult['found']) {
// 対象レコードなしの場合
$result = $this->handleNoTargetRecord($settlement, $contractResult);
$this->createBatchLog($settlement, null, null, true, null, null, null, $result['message']);
return $result;
}
if ($contractResult['already_processed']) {
// 登録済みの場合
$result = $this->handleAlreadyProcessed($settlement, $contractResult);
$contract = $contractResult['contract'];
$this->createBatchLog($settlement, $contract, null, true, null, null, null, $result['message']);
return $result;
}
$contract = $contractResult['contract'];
// 【判断1】授受状態チェック
$statusResult = $this->judgeReceiptStatus($settlement, $contract);
if (!$statusResult['valid']) {
// 授受済みの場合(仕様: JOB5メール送信 → JOB6バッチログ → 終了)
$result = $this->handleInvalidStatus($settlement, $contract, $statusResult);
// 【JOB5】メール送信授受済みでも送信
$mailResult = $this->sendUserNotificationMail($settlement, $contract);
$mailCommentSuffix = $mailResult['batch_comment_suffix'] ?? null;
// 【JOB6】バッチログ授受済みメッセージを直接statusCommentとして渡す
$this->createBatchLog(
$settlement, $contract, null, true, null, $mailCommentSuffix,
null, $result['message']
);
return $result;
}
// 【JOB3-0】SHJ-4C 車室割り当て(新規のみ)
// 仕様:定期契約継続フラグ(update_flag) = 2新規の場合に呼び出す
// ※異常があったとしても、処理は継続する
$shjFourCResult = null;
if ((int) $contract->update_flag === 2) {
$shjFourCResult = $this->triggerShjFourC($contract);
}
// 【JOB3-1】契約更新処理実行SHJ-4C結果を含む
// 仕様フロー順: JOB3-0 → JOB3-1/3-4 → JOB3-STEP1金額チェック
$updateResult = $this->executeContractUpdate($settlement, $contract, $shjFourCResult);
// 【判断2】金額チェック仕様のJOB3-STEP1
$amountResult = $this->judgeAmountComparison($settlement, $contract);
// 副作用処理実行SHJ-4C結果をSHJ-13に渡す
$sideEffectResult = $this->executeSideEffects($settlement, $contract, $amountResult, $updateResult, $shjFourCResult);
$result = [
'success' => true,
'settlement_transaction_id' => $settlementTransactionId,
'contract_id' => $contract->contract_id,
'contract_payment_number' => $settlement->contract_payment_number,
'amount_comparison' => $amountResult['comparison'],
'contract_updated' => $updateResult['updated'],
'side_effects' => $sideEffectResult,
'execution_time' => now()->diffInSeconds($startTime),
];
Log::info('SHJ-4B 決済トランザクション処理完了', $result);
// 【処理】バッチ処理ログ作成SHJ-8呼び出し
$mailCommentSuffix = $sideEffectResult['user_mail']['batch_comment_suffix'] ?? null;
$photoDeletionResult = $sideEffectResult['photo_deletion'] ?? null;
$this->createBatchLog($settlement, $contract, $amountResult, true, null, $mailCommentSuffix, $photoDeletionResult);
return $result;
} catch (\Throwable $e) {
Log::error('SHJ-4B 決済トランザクション処理失敗', [
'settlement_transaction_id' => $settlementTransactionId,
'context' => $context,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
// エラー時のバッチログ作成
try {
$settlement = SettlementTransaction::find($settlementTransactionId);
if ($settlement) {
$this->createBatchLog($settlement, null, null, false, $e->getMessage());
}
} catch (\Throwable $logError) {
Log::error('SHJ-4B バッチログ作成エラー', ['error' => $logError->getMessage()]);
}
throw $e;
}
}
/**
* ウェルネットPUSHデータ処理入口
*
* 仕様順序: JOB1(SQL-1 契約検索) → JOB2(SQL-2 重複チェック → SQL-3 INSERT) → 既存処理
* 既存の processSettlementTransaction(id) は再処理・後方互換の入口として残す
*
* @param array $wellnetData ウェルネットからの生データ
* @return array 処理結果
*/
public function processWellnetPush(array $wellnetData): array
{
$contractPaymentNumber = $wellnetData['contract_payment_number'];
Log::info('SHJ-4B ウェルネットPUSHデータ処理開始', [
'contract_payment_number' => $contractPaymentNumber,
]);
// 【JOB1】SQL-1: 定期契約マスタの対象レコード取得judgeTargetContractと同一条件
$contract = DB::table('regular_contract as T1')
->join('park as T2', 'T1.park_id', '=', 'T2.park_id')
->join('price_a as T4', function($join) {
$join->on('T1.price_parkplaceid', '=', 'T4.price_parkplaceid')
->on('T1.park_id', '=', 'T4.park_id');
})
->where('T1.contract_payment_number', $contractPaymentNumber)
->where('T1.contract_cancel_flag', '!=', 1)
->whereNotNull('T1.contract_flag')
->select('T1.contract_id')
->first();
// JOB1-STEP1: 対象レコード判定
if (!$contract) {
Log::warning('SHJ-4B JOB1: 受付番号不正(対象契約なし)', [
'contract_payment_number' => $contractPaymentNumber,
]);
// 仕様: JOB6 バッチログ → 終了settlement_transaction未作成
$statusComment = "受付番号が不正です。(受付番号:{$contractPaymentNumber}";
$this->createWellnetPushBatchLog($statusComment);
return [
'success' => true,
'result' => 'no_target',
'message' => $statusComment,
];
}
// 【JOB2】SQL-2: 重複チェック
// 補足: 受付番号は contract_payment_numberrnoで照合する
$existCount = DB::table('settlement_transaction')
->where('contract_payment_number', $contractPaymentNumber)
->count();
if ($existCount >= 1) {
Log::info('SHJ-4B SQL-2: 既に登録済み', [
'contract_payment_number' => $contractPaymentNumber,
]);
return [
'success' => true,
'result' => 'already_registered',
'message' => '決済トランザクションは既に登録済みです',
];
}
// 【SQL-3】決済トランザクション挿入<JOB1>.定期契約ID を使用)
$settlementId = DB::table('settlement_transaction')->insertGetId([
'contract_id' => $contract->contract_id,
'status' => $wellnetData['status'] ?? null,
'pay_code' => $wellnetData['pay_code'] ?? null,
'contract_payment_number' => $contractPaymentNumber,
'corp_code' => $wellnetData['corp_code'] ?? null,
'mms_date' => $wellnetData['mms_date'] ?? null,
'cvs_code' => $wellnetData['cvs_code'] ?? null,
'shop_code' => $wellnetData['shop_code'] ?? null,
'pay_date' => $wellnetData['pay_date'] ?? null,
'settlement_amount' => $wellnetData['settlement_amount'] ?? null,
'stamp_flag' => $wellnetData['stamp_flag'] ?? null,
'md5_string' => $wellnetData['md5_string'] ?? null,
'created_at' => now(),
'updated_at' => now(),
]);
Log::info('SHJ-4B SQL-3: 決済トランザクション登録', [
'settlement_transaction_id' => $settlementId,
'contract_payment_number' => $contractPaymentNumber,
'contract_id' => $contract->contract_id,
]);
// JOB2-STEP2以降: 既存フローへ
return $this->processSettlementTransaction($settlementId);
}
/**
* 決済トランザクション取得
*
* @param int $settlementTransactionId
* @return SettlementTransaction
* @throws \RuntimeException
*/
private function getSettlementTransaction(int $settlementTransactionId): SettlementTransaction
{
$settlement = SettlementTransaction::find($settlementTransactionId);
if (!$settlement) {
throw new \RuntimeException("SettlementTransaction not found: {$settlementTransactionId}");
}
Log::info('SHJ-4B 決済トランザクション取得成功', [
'settlement_transaction_id' => $settlementTransactionId,
'contract_payment_number' => $settlement->contract_payment_number,
'settlement_amount' => $settlement->settlement_amount,
'pay_date' => $settlement->pay_date,
]);
return $settlement;
}
/**
* 【処理1】定期契約マスタの対象レコード取得
* 【判断0】取得判定登録済み判定を含む
*
* @param SettlementTransaction $settlement
* @return array
*/
private function judgeTargetContract(SettlementTransaction $settlement): array
{
Log::info('SHJ-4B 処理1: 定期契約マスタの対象レコード取得開始', [
'settlement_transaction_id' => $settlement->settlement_transaction_id,
'contract_payment_number' => $settlement->contract_payment_number,
]);
// 仕様書要件のSQL構造に基づく対象レコード取得
// regular_contract T1 inner join park T2 inner join price_a T4
$contractQuery = DB::table('regular_contract as T1')
->select([
'T1.contract_id',
'T1.old_contract_id',
'T1.park_id',
'T1.user_id',
'T1.contract_flag',
'T1.billing_amount',
'T4.price_ptypeid as ptype_id',
'T1.psection_id',
'T1.zone_id',
'T1.update_flag',
'T1.reserve_id',
'T1.contract_payment_number',
'T1.contract_payment_day',
'T1.contract_periods',
'T1.contract_periode',
'T1.contract_created_at',
'T1.contract_cancel_flag',
'T2.park_name',
'T2.immediate_use_permit',
'T2.update_grace_period_start_date',
'T2.update_grace_period_end_date',
'T2.parking_start_grace_period',
'T4.price_month',
'T4.price'
])
->join('park as T2', 'T1.park_id', '=', 'T2.park_id')
->join('price_a as T4', function($join) {
$join->on('T1.price_parkplaceid', '=', 'T4.price_parkplaceid')
->on('T1.park_id', '=', 'T4.park_id'); // 仕様書要件の第2条件
})
->where('T1.contract_payment_number', $settlement->contract_payment_number)
->where('T1.contract_cancel_flag', '!=', 1) // 解約されていない
->whereNotNull('T1.contract_flag') // 状態が設定済み
->first();
if (!$contractQuery) {
Log::warning('SHJ-4B 判断0: 対象レコードなし', [
'contract_payment_number' => $settlement->contract_payment_number,
'settlement_transaction_id' => $settlement->settlement_transaction_id,
]);
return [
'found' => false,
'contract' => null,
'reason' => '対象レコードなし',
'message' => "受付番号が不正です。(受付番号:{$settlement->contract_payment_number}",
];
}
// 登録済み判定
$isAlreadyProcessed = $this->checkAlreadyProcessed($contractQuery, $settlement);
if ($isAlreadyProcessed['processed']) {
Log::info('SHJ-4B 判断0: 登録済み検出', [
'contract_id' => $contractQuery->contract_id,
'contract_payment_number' => $settlement->contract_payment_number,
'reason' => $isAlreadyProcessed['reason'],
]);
return [
'found' => true,
'contract' => $contractQuery,
'already_processed' => true,
'reason' => '登録済み',
'message' => "この決済は既に処理済みです: " . $isAlreadyProcessed['reason'],
];
}
Log::info('SHJ-4B 判断0: 対象契約取得成功', [
'contract_id' => $contractQuery->contract_id,
'contract_payment_number' => $settlement->contract_payment_number,
'billing_amount' => $contractQuery->billing_amount,
'contract_flag' => $contractQuery->contract_flag,
'park_name' => $contractQuery->park_name,
'price_month' => $contractQuery->price_month,
]);
return [
'found' => true,
'contract' => $contractQuery,
'already_processed' => false,
'reason' => '対象契約取得成功',
'message' => "契約ID {$contractQuery->contract_id} を取得しました",
];
}
/**
* 登録済み判定
*
* 複数の条件で既に処理済みかを判定
*
* @param object $contract
* @param SettlementTransaction $settlement
* @return array
*/
private function checkAlreadyProcessed($contract, SettlementTransaction $settlement): array
{
// 条件1: contract_payment_dayが既に設定済みで、今回の支払日以降
if (!empty($contract->contract_payment_day)) {
$existingPaymentDate = Carbon::parse($contract->contract_payment_day);
$currentPaymentDate = Carbon::parse($settlement->pay_date);
if ($existingPaymentDate->gte($currentPaymentDate)) {
return [
'processed' => true,
'reason' => "既に支払日 {$existingPaymentDate->format('Y-m-d')} が設定済み",
];
}
}
// 条件2: 同一の決済条件(contract_payment_number + pay_date + settlement_amount)が
// 既に他のsettlement_transactionで処理済み
$existingTransaction = SettlementTransaction::where('contract_payment_number', $settlement->contract_payment_number)
->where('pay_date', $settlement->pay_date)
->where('settlement_amount', $settlement->settlement_amount)
->where('settlement_transaction_id', '!=', $settlement->settlement_transaction_id)
->first();
if ($existingTransaction) {
return [
'processed' => true,
'reason' => "同一条件の決済トランザクション {$existingTransaction->settlement_transaction_id} が既に存在",
];
}
// 条件3: bat_job_logで同一決済の処理完了記録があるか
// status_commentに決済トランザクションIDが含まれているかチェック
$existingBatchLog = BatJobLog::where('process_name', 'SHJ-4B')
->where('status', 'success')
->where('status_comment', 'like', '%settlement_transaction_id:' . $settlement->settlement_transaction_id . '%')
->exists();
if ($existingBatchLog) {
return [
'processed' => true,
'reason' => "bat_job_logに処理完了記録が存在",
];
}
return [
'processed' => false,
'reason' => '未処理',
];
}
/**
* 【判断1】授受状態チェック
*
* @param SettlementTransaction $settlement
* @param object $contract
* @return array
*/
private function judgeReceiptStatus(SettlementTransaction $settlement, $contract): array
{
// 仕様JOB2-STEP2: 授受フラグ = 0 の場合、処理続行
if ((int) $contract->contract_flag === 0) {
Log::info('SHJ-4B 判断1: 授受フラグ=0 処理続行', [
'settlement_transaction_id' => $settlement->settlement_transaction_id,
'contract_id' => $contract->contract_id,
]);
return [
'valid' => true,
'reason' => '授受フラグ未処理',
'message' => '授受フラグチェックに合格しました',
];
}
// その他の場合(授受済み)
$message = sprintf(
'支払いステータスチェック授受済みです。定期契約ID%s',
$contract->contract_id
);
Log::warning('SHJ-4B 判断1: 授受済み', [
'settlement_transaction_id' => $settlement->settlement_transaction_id,
'contract_id' => $contract->contract_id,
'contract_flag' => $contract->contract_flag,
]);
return [
'valid' => false,
'reason' => '授受済み',
'message' => $message,
];
}
/**
* 【判断2】金額チェック
*
* @param SettlementTransaction $settlement
* @param object $contract
* @return array
*/
private function judgeAmountComparison(SettlementTransaction $settlement, $contract): array
{
// 仕様書要件: 請求額=授受額の厳密比較
$billingAmount = (int) $contract->billing_amount; // 整数として比較
$settlementAmount = (int) $settlement->settlement_amount; // 整数として比較
$difference = $settlementAmount - $billingAmount;
if ($difference === 0) {
$comparison = self::AMOUNT_MATCH;
$result = '正常(金額一致)';
} elseif ($difference < 0) {
$comparison = self::AMOUNT_SHORTAGE;
$result = '授受過少';
} else {
$comparison = self::AMOUNT_EXCESS;
$result = '授受超過';
}
Log::info('SHJ-4B 判断2: 金額チェック完了', [
'settlement_transaction_id' => $settlement->settlement_transaction_id,
'contract_id' => $contract->contract_id,
'billing_amount' => $billingAmount,
'settlement_amount' => $settlementAmount,
'difference' => $difference,
'comparison' => $comparison,
'result' => $result,
]);
return [
'comparison' => $comparison,
'result' => $result,
'billing_amount' => $billingAmount,
'settlement_amount' => $settlementAmount,
'difference' => $difference,
'message' => "請求額: {$billingAmount}円, 授受額: {$settlementAmount}円, 結果: {$result}",
];
}
/**
* 登録済み処理
*
* @param SettlementTransaction $settlement
* @param array $contractResult
* @return array
*/
private function handleAlreadyProcessed(SettlementTransaction $settlement, array $contractResult): array
{
Log::info('SHJ-4B 登録済み処理', [
'settlement_transaction_id' => $settlement->settlement_transaction_id,
'contract_id' => $contractResult['contract']->contract_id,
'reason' => $contractResult['reason'],
]);
return [
'success' => true,
'settlement_transaction_id' => $settlement->settlement_transaction_id,
'contract_id' => $contractResult['contract']->contract_id,
'contract_payment_number' => $settlement->contract_payment_number,
'result' => 'already_processed',
'reason' => $contractResult['reason'],
'message' => $contractResult['message'],
'skipped' => true,
];
}
/**
* 【SQL-4】シール印刷可能日を計算仕様パターンA/B準拠
*
* パターン判定:
* - 即利用許可=1 → 入金日の年月日
* - 更新期間開始日 <= 更新期間終了日パターンA:
* - A-1: 駐輪開始猶予期間 > 本日の日 → 入金日の年月 + 猶予期間日
* - A-2: それ以外 → 入金日の年月日
* - 更新期間開始日 > 更新期間終了日パターンB→ 入金日の年月日
*
* @param SettlementTransaction $settlement
* @param object $contract
* @return string Y-m-d形式の日付
*/
private function calculatePrintableDate(SettlementTransaction $settlement, $contract): string
{
$payDate = Carbon::parse($settlement->pay_date);
// 契約後即利用許可 = 1
if ((int) ($contract->immediate_use_permit ?? 0) === 1) {
return $payDate->format('Y-m-d');
}
$startDate = (int) ($contract->update_grace_period_start_date ?? 0);
$endDate = (int) ($contract->update_grace_period_end_date ?? 0);
$gracePeriod = (int) ($contract->parking_start_grace_period ?? 0);
$todayDay = (int) now()->format('d');
// パターンA: 更新期間開始日 <= 更新期間終了日
if ($startDate <= $endDate) {
if ($gracePeriod > $todayDay) {
// A-1: 入金日の年月 + 猶予期間日
return $payDate->format('Y-m') . '-' . str_pad($gracePeriod, 2, '0', STR_PAD_LEFT);
}
// A-2: 入金日の年月日
return $payDate->format('Y-m-d');
}
// パターンB: 全サブケース同一 → 入金日の年月日
return $payDate->format('Y-m-d');
}
/**
* 新規契約判定
*
* @param object $contract
* @return bool
*/
private function isNewContract($contract): bool
{
if (empty($contract->contract_created_at)) {
return false;
}
$createdAt = Carbon::parse($contract->contract_created_at);
$thirtyDaysAgo = Carbon::now()->subDays(30);
// 作成から30日以内を新規とみなす調整可能
$isNew = $createdAt->gte($thirtyDaysAgo);
Log::info('SHJ-4B 新規契約判定', [
'contract_id' => $contract->contract_id,
'contract_created_at' => $createdAt->format('Y-m-d H:i:s'),
'is_new' => $isNew,
'days_since_created' => $createdAt->diffInDays(Carbon::now()),
]);
return $isNew;
}
/**
* 【JOB3-1】決済授受および写真削除 + 定期契約マスタ、定期予約マスタ更新
*
* @param SettlementTransaction $settlement
* @param object $contract
* @param array|null $shjFourCResult SHJ-4C車室割り当て結果新規のみ
* @return array
*/
private function executeContractUpdate(
SettlementTransaction $settlement,
$contract,
?array $shjFourCResult = null
): array {
$updateData = [];
$updated = false;
try {
DB::transaction(function() use ($settlement, $contract, $shjFourCResult, &$updateData, &$updated) {
// 【SQL-4】基本更新項目仕様準拠
$updateData = [
'contract_payment_day' => Carbon::parse($settlement->pay_date)->format('Y-m-d H:i:s'),
'contract_money' => $settlement->settlement_amount,
'contact_shop_code' => $settlement->shop_code,
'contract_cvs_class' => $settlement->cvs_code,
'contract_flag' => 1,
'settlement_transaction_id' => $settlement->settlement_transaction_id,
'contract_permission' => 1,
'printable_date' => $this->calculatePrintableDate($settlement, $contract),
'contract_payment_number' => $settlement->contract_payment_number,
'updated_at' => now(),
];
// 【新規】SHJ-4Cの結果を反映仕様SQL-4準拠
if ((int) $contract->update_flag === 2 && $shjFourCResult
&& ($shjFourCResult['success'] ?? false)
&& !empty($shjFourCResult['zone_id']) && !empty($shjFourCResult['pplace_no'])) {
$updateData['zone_id'] = $shjFourCResult['zone_id'];
$updateData['pplace_no'] = $shjFourCResult['pplace_no'];
}
// 【SQL-4】定期契約マスタ更新
$affectedRows = DB::table('regular_contract')
->where('contract_id', $contract->contract_id)
->update($updateData);
$updated = $affectedRows > 0;
// 【SQL-5】契約元の定期契約マスタを更新旧契約の更新済フラグ
if (!empty($contract->old_contract_id)) {
DB::table('regular_contract')
->where('contract_id', $contract->old_contract_id)
->update(['contract_renewal' => 1, 'updated_at' => now()]);
Log::info('SHJ-4B SQL-5 旧契約更新済フラグ更新', [
'old_contract_id' => $contract->old_contract_id,
'contract_id' => $contract->contract_id,
]);
}
// 【SQL-8】定期予約マスタ更新reserve_idがnull以外の場合
if (!empty($contract->reserve_id)) {
DB::table('reserve')
->where('reserve_id', $contract->reserve_id)
->update([
'contract_id' => $contract->contract_id,
'contract_created_at' => now(),
'valid_flag' => 0,
'updated_at' => now(),
]);
Log::info('SHJ-4B 定期予約マスタ更新完了', [
'reserve_id' => $contract->reserve_id,
'contract_id' => $contract->contract_id,
]);
}
Log::info('SHJ-4B 定期契約マスタ更新完了', [
'contract_id' => $contract->contract_id,
'settlement_transaction_id' => $settlement->settlement_transaction_id,
'update_data' => $updateData,
'affected_rows' => $affectedRows,
]);
});
return [
'updated' => $updated,
'update_data' => $updateData,
'message' => $updated ? '契約更新に成功しました' : '契約更新対象が見つかりませんでした',
];
} catch (\Throwable $e) {
Log::error('SHJ-4B 契約更新処理失敗', [
'contract_id' => $contract->contract_id,
'settlement_transaction_id' => $settlement->settlement_transaction_id,
'error' => $e->getMessage(),
]);
throw $e;
}
}
/**
* 副作用処理実行
*
* 決済授受および写真削除、新規連動等の処理
*
* @param SettlementTransaction $settlement
* @param object $contract
* @param array $amountResult
* @param array $updateResult
* @param array|null $shjFourCResult SHJ-4C車室割り当て結果新規のみ
* @return array
*/
private function executeSideEffects(
SettlementTransaction $settlement,
$contract,
array $amountResult,
array $updateResult,
?array $shjFourCResult = null
): array {
$sideEffects = [];
try {
// 【JOB3-2】写真削除処理SQL-4/SQL-5の後、金額比較結果に関わらず実行
$sideEffects['photo_deletion'] = $this->executePhotoDeletion($contract);
// 【JOB3-3】写真削除バッチログ独立SHJ-8呼出し
$this->createPhotoDeletionBatchLog($sideEffects['photo_deletion']);
// 【JOB3-STEP1】SHJ-13契約台数追加新規 かつ shortage以外
// 仕様shortage → SHJ-13呼出なし、match/excess → SHJ-13呼出
if ((int) $contract->update_flag === 2
&& $amountResult['comparison'] !== self::AMOUNT_SHORTAGE) {
$sideEffects['shj13_trigger'] = $this->triggerShjThirteen($contract, $shjFourCResult);
}
// 【処理4】異常時のオペレーターキュー登録処理
if ($amountResult['comparison'] !== self::AMOUNT_MATCH) {
$sideEffects['operator_queue'] = $this->registerToOperatorQueue($settlement, $contract, $amountResult);
}
// 【JOB5】利用者メール送信処理仕様: フロー到達時は一律送信)
$sideEffects['user_mail'] = $this->sendUserNotificationMail($settlement, $contract, $amountResult);
Log::info('SHJ-4B 副作用処理完了', [
'settlement_transaction_id' => $settlement->settlement_transaction_id,
'contract_id' => $contract->contract_id,
'side_effects' => array_keys($sideEffects),
]);
return $sideEffects;
} catch (\Throwable $e) {
Log::error('SHJ-4B 副作用処理失敗', [
'settlement_transaction_id' => $settlement->settlement_transaction_id,
'contract_id' => $contract->contract_id,
'error' => $e->getMessage(),
]);
// 副作用処理の失敗はメイン処理を止めない
return ['error' => $e->getMessage()];
}
}
/**
* 対象レコードなしの場合の処理
*
* @param SettlementTransaction $settlement
* @param array $contractResult
* @return array
*/
private function handleNoTargetRecord(SettlementTransaction $settlement, array $contractResult): array
{
Log::warning('SHJ-4B 対象レコードなし処理', [
'settlement_transaction_id' => $settlement->settlement_transaction_id,
'contract_payment_number' => $settlement->contract_payment_number,
'reason' => $contractResult['reason'],
]);
// TODO: 必要に応じて管理者通知やオペレーターキューへの登録
return [
'success' => true, // エラーではなく、正常な結果として扱う
'settlement_transaction_id' => $settlement->settlement_transaction_id,
'contract_payment_number' => $settlement->contract_payment_number,
'result' => 'no_target',
'reason' => $contractResult['reason'],
'message' => $contractResult['message'],
'action_required' => '管理者による手動確認が必要です',
];
}
/**
* 授受状態異常の場合の処理
*
* @param SettlementTransaction $settlement
* @param RegularContract $contract
* @param array $statusResult
* @return array
*/
private function handleInvalidStatus(
SettlementTransaction $settlement,
$contract,
array $statusResult
): array {
Log::warning('SHJ-4B 授受状態異常処理', [
'settlement_transaction_id' => $settlement->settlement_transaction_id,
'contract_id' => $contract->contract_id,
'reason' => $statusResult['reason'],
]);
return [
'success' => true, // 授受済みは仕様上エラーではなく正常分岐
'settlement_transaction_id' => $settlement->settlement_transaction_id,
'contract_id' => $contract->contract_id,
'result' => 'already_received',
'reason' => $statusResult['reason'],
'message' => $statusResult['message'],
];
}
/**
* 写真削除処理
*
* @param object $contract
* @return array
*/
private function executePhotoDeletion($contract): array
{
// 【SQL-6】利用者の写真ファイル名取得
$userData = DB::table('regular_contract as T1')
->join('user as T2', 'T1.user_id', '=', 'T2.user_seq')
->where('T1.contract_payment_number', $contract->contract_payment_number)
->where('T1.contract_flag', 1)
->select('T2.user_id', 'T2.user_seq', 'T2.user_name', 'T2.photo_filename1', 'T2.photo_filename2')
->first();
if (!$userData) {
Log::info('SHJ-4B 写真削除: 対象利用者なし', [
'contract_id' => $contract->contract_id,
]);
return ['executed' => false, 'message' => '対象利用者なし'];
}
$deletedFiles = [];
// ファイル削除
foreach (['photo_filename1', 'photo_filename2'] as $field) {
if (!empty($userData->$field)) {
$path = 'photo/' . $userData->$field;
if (Storage::disk('public')->exists($path)) {
Storage::disk('public')->delete($path);
$deletedFiles[] = $userData->$field;
}
}
}
// 【SQL-7】利用者の写真ファイル名をNULLに更新
$userUpdateQuery = DB::table('user');
if (!empty($userData->user_id)) {
// 仕様書上の利用者IDuser.user_idで更新
$userUpdateQuery->where('user_id', $userData->user_id);
} else {
// user_id未設定データの後方互換
$userUpdateQuery->where('user_seq', $userData->user_seq);
}
$userUpdateQuery->update([
'photo_filename1' => null,
'photo_filename2' => null,
'updated_at' => now(),
]);
Log::info('SHJ-4B 写真削除処理完了', [
'user_seq' => $userData->user_seq,
'deleted_files' => $deletedFiles,
]);
return [
'executed' => true,
'user_id' => $userData->user_id,
'user_seq' => $userData->user_seq,
'user_name' => $userData->user_name,
'photo_filename1' => $userData->photo_filename1,
'photo_filename2' => $userData->photo_filename2,
'deleted_files' => $deletedFiles,
];
}
/**
* ウェルネットPUSH処理用バッチログ作成settlement_transaction未作成時
*
* processWellnetPush内でJOB1不合格受付番号不正の場合に使用
*
* @param string $statusComment ステータスコメント
* @return void
*/
private function createWellnetPushBatchLog(string $statusComment): void
{
try {
$device = Device::orderBy('device_id')->first();
$deviceId = $device ? $device->device_id : 1;
$today = now()->format('Y/m/d');
$this->shjEightService->execute(
$deviceId,
'SHJ-4B',
'SHJ-4支払いステータスチェック',
'success',
$statusComment,
$today,
$today
);
} catch (\Exception $e) {
Log::error('SHJ-4B ウェルネットPUSHバッチログ作成エラー', [
'error' => $e->getMessage(),
]);
}
}
/**
* 【JOB3-3】写真削除バッチログ作成独立SHJ-8呼出し
*
* @param array $photoDeletionResult executePhotoDeletion()の戻り値
* @return void
*/
private function createPhotoDeletionBatchLog(array $photoDeletionResult): void
{
try {
$device = Device::orderBy('device_id')->first();
$deviceId = $device ? $device->device_id : 1;
$today = now()->format('Y/m/d');
if ($photoDeletionResult['executed'] ?? false) {
$status = 'success';
$userId = $photoDeletionResult['user_id'] ?? '';
$userName = $photoDeletionResult['user_name'] ?? '';
$file1 = $photoDeletionResult['photo_filename1'] ?? '';
$file2 = $photoDeletionResult['photo_filename2'] ?? '';
$statusComment = "利用者ID:{$userId}、利用者名:{$userName}、ファイル名:{$file1},{$file2}";
} else {
$status = 'error';
$statusComment = $photoDeletionResult['message'] ?? '写真削除対象なし';
}
$this->shjEightService->execute(
$deviceId,
'SHJ-4B',
'SHJ-4B本人確認写真削除',
$status,
$statusComment,
$today,
$today
);
} catch (\Exception $e) {
Log::error('SHJ-4B JOB3-3 写真削除バッチログ作成エラー', [
'error' => $e->getMessage(),
]);
}
}
/**
* 【JOB3-0】SHJ-4C 車室割り当て処理(新規のみ)
*
* 仕様:定期契約継続フラグ = 2新規の場合に呼び出す
* ※異常があったとしても、処理は継続する
*
* @param object $contract 契約データ
* @return array|null 処理結果異常時はnull
*/
private function triggerShjFourC($contract): ?array
{
Log::info('SHJ-4B SHJ-4C車室割り当て処理実行', [
'contract_id' => $contract->contract_id,
'park_id' => $contract->park_id,
'ptype_id' => $contract->ptype_id,
'psection_id' => $contract->psection_id,
]);
try {
// SHJ-4Cサービス実行
$result = $this->shjFourCService->executeRoomAllocation(
(int) $contract->park_id,
(int) $contract->ptype_id,
(int) $contract->psection_id
);
Log::info('SHJ-4B SHJ-4C車室割り当て処理完了', [
'contract_id' => $contract->contract_id,
'result' => $result,
]);
return $result;
} catch (\Throwable $e) {
// ※異常があったとしても、処理は継続する
Log::error('SHJ-4B SHJ-4C車室割り当て処理エラー', [
'contract_id' => $contract->contract_id,
'error' => $e->getMessage(),
]);
return null;
}
}
/**
* SHJ-13実行処理新規のみ
*
* ShjThirteenServiceを使用した契約台数追加処理
* 仕様パラメーター4 ゾーンID = 【SHJ-4C.ゾーンID】
*
* @param object $contract
* @param array|null $shjFourCResult SHJ-4C車室割り当て結果
* @return array
*/
private function triggerShjThirteen($contract, ?array $shjFourCResult = null): array
{
// 仕様準拠ゾーンID = SHJ-4C.ゾーンID
$zoneId = ($shjFourCResult && !empty($shjFourCResult['zone_id']))
? $shjFourCResult['zone_id']
: $contract->zone_id;
Log::info('SHJ-4B SHJ-13実行処理', [
'contract_id' => $contract->contract_id,
'park_id' => $contract->park_id,
'psection_id' => $contract->psection_id,
'ptype_id' => $contract->ptype_id,
'zone_id' => $zoneId,
]);
try {
// 契約データ準備仕様パラメーター4 ゾーンID = SHJ-4C.ゾーンID
$contractData = [
'contract_id' => $contract->contract_id,
'park_id' => $contract->park_id,
'psection_id' => $contract->psection_id,
'ptype_id' => $contract->ptype_id,
'zone_id' => $zoneId,
];
// ShjThirteenService実行
$shjThirteenService = app(ShjThirteenService::class);
$result = $shjThirteenService->execute($contractData);
Log::info('SHJ-4B SHJ-13実行完了', [
'contract_id' => $contract->contract_id,
'result' => $result,
]);
return $result;
} catch (\Throwable $e) {
Log::error('SHJ-4B SHJ-13実行エラー', [
'contract_id' => $contract->contract_id,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
return [
'result' => 1,
'error_code' => $e->getCode() ?: 1999,
'error_message' => $e->getMessage(),
'stack_trace' => $e->getTraceAsString(),
];
}
}
/**
* 【処理5】利用者メール送信処理
*
* SHJ-4B仕様準拠:
* 1. 利用者マスタよりメールアドレス、予備メールアドレスを取得
* 2. SHJ-7メール送信を呼び出し使用プログラムID: 205
* 3. 処理結果判定:
* - result = 0 (正常): バッチコメントに "メール正常終了件数1" を追加
* - その他: バッチコメントに "メール異常終了件数1、" + error_info を追加
*
* @param SettlementTransaction $settlement
* @param object $contract
* @param array $amountResult
* @return array 処理結果 ['success' => bool, 'mail_status' => string, 'batch_comment_suffix' => string]
*/
private function sendUserNotificationMail(SettlementTransaction $settlement, $contract, ?array $amountResult = null): array
{
try {
// 【処理5】利用者マスタよりメールアドレス、予備メールアドレスを取得する
$user = User::select('user_name', 'user_primemail', 'user_submail')
->where('user_seq', $contract->user_id)
->first();
if (!$user) {
Log::error('SHJ-4B 利用者メール送信処理: 利用者情報取得失敗', [
'user_id' => $contract->user_id,
'contract_id' => $contract->contract_id,
]);
return [
'success' => false,
'mail_status' => 'user_not_found',
'batch_comment_suffix' => 'メール異常終了件数1、利用者情報取得失敗'
];
}
$mailAddress = $user->user_primemail ?? '';
$backupMailAddress = $user->user_submail ?? '';
Log::info('SHJ-4B 利用者メール送信処理開始', [
'contract_id' => $contract->contract_id,
'user_id' => $contract->user_id,
'user_name' => $user->user_name,
'settlement_transaction_id' => $settlement->settlement_transaction_id,
'mail_address' => $mailAddress,
'amount_comparison' => $amountResult['comparison'] ?? 'already_received',
]);
// 共通処理「SHJ-7メール送信」を呼び出し
// 使用プログラムID: 205仕様書準拠
$mailResult = $this->mailSendService->executeMailSend(
$mailAddress,
$backupMailAddress,
205 // SHJ-4B仕様: 使用プログラムID = 205
);
// SHJ-7仕様準拠: result === 0 が正常、それ以外は異常
if (($mailResult['result'] ?? 1) === 0) {
// 【正常終了】バッチコメントに "メール正常終了件数1" を設定
Log::info('SHJ-4B 利用者メール送信成功', [
'contract_id' => $contract->contract_id,
'user_id' => $contract->user_id,
'mail_address' => $mailAddress,
]);
return [
'success' => true,
'mail_status' => 'sent',
'batch_comment_suffix' => 'メール正常終了件数1'
];
} else {
// 【異常終了】バッチコメントに "メール異常終了件数1、" + error_info を設定
$errorInfo = $mailResult['error_info'] ?? 'メール送信失敗';
Log::error('SHJ-4B 利用者メール送信失敗', [
'contract_id' => $contract->contract_id,
'user_id' => $contract->user_id,
'mail_address' => $mailAddress,
'error_info' => $errorInfo,
]);
return [
'success' => false,
'mail_status' => 'failed',
'batch_comment_suffix' => "メール異常終了件数1、{$errorInfo}"
];
}
} catch (\Exception $e) {
Log::error('SHJ-4B 利用者メール送信処理例外エラー', [
'contract_id' => $contract->contract_id,
'user_id' => $contract->user_id,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
return [
'success' => false,
'mail_status' => 'exception',
'batch_comment_suffix' => 'メール異常終了件数1、システムエラー: ' . $e->getMessage()
];
}
}
/**
* オペレーターキューへの登録
*
* @param SettlementTransaction $settlement
* @param object $contract
* @param array $amountResult
* @return array
*/
private function registerToOperatorQueue(
SettlementTransaction $settlement,
$contract,
array $amountResult
): array {
// 【SQL-9】キュー種別: shortage=8(支払い催促), excess=6(返金処理)
$queClass = $amountResult['comparison'] === self::AMOUNT_SHORTAGE ? 8 : 6;
$queId = DB::table('operator_que')->insertGetId([
'que_class' => $queClass,
'user_id' => $contract->user_id,
'contract_id' => $contract->contract_id,
'park_id' => $contract->park_id,
'que_comment' => '収納請求金額照合エラー',
'que_status' => 1,
'que_status_comment' => '',
'work_instructions' => '',
'operator_id' => 'SHJ-4',
'created_at' => now(),
'updated_at' => now(),
]);
Log::info('SHJ-4B オペレーターキュー登録完了', [
'que_id' => $queId,
'que_class' => $queClass,
'contract_id' => $contract->contract_id,
]);
return [
'registered' => true,
'que_id' => $queId,
'que_class' => $queClass,
];
}
/**
* 【処理6】バッチ処理ログ作成
*
* SHJ-8サービスを呼び出してbat_job_logに記録
*
* @param SettlementTransaction $settlement
* @param object|null $contract
* @param array|null $amountResult
* @param bool $isSuccess
* @param string|null $errorMessage
* @param string|null $mailCommentSuffix メール送信結果コメント
* @param array|null $photoDeletionResult 写真削除結果SQL-6情報含む
* @return void
*/
private function createBatchLog(
SettlementTransaction $settlement,
$contract = null,
?array $amountResult = null,
bool $isSuccess = true,
?string $errorMessage = null,
?string $mailCommentSuffix = null,
?array $photoDeletionResult = null,
?string $statusCommentOverride = null
): void {
try {
$device = Device::orderBy('device_id')->first();
$deviceId = $device ? $device->device_id : 1;
$today = now()->format('Y/m/d');
// ステータスコメント生成(内部変数.バッチコメント)
if ($statusCommentOverride) {
// 直接指定(授受済み等)
$statusComment = $statusCommentOverride;
} elseif ($errorMessage) {
// エラー時
$statusComment = "支払いステータスチェックエラー決済トランザクションID{$settlement->settlement_transaction_id} - {$errorMessage}";
} elseif ($amountResult) {
// 正常処理時
switch ($amountResult['comparison']) {
case self::AMOUNT_MATCH:
// 仕様: OK + 利用者情報 + 写真ファイル名
$userId = $photoDeletionResult['user_id'] ?? '';
$userName = $photoDeletionResult['user_name'] ?? '';
$file1 = $photoDeletionResult['photo_filename1'] ?? '';
$file2 = $photoDeletionResult['photo_filename2'] ?? '';
$statusComment = "支払いステータスチェックOK、決済トランザクションID{$settlement->settlement_transaction_id}、利用者ID:{$userId}、利用者名:{$userName}、ファイル名:{$file1},{$file2}";
break;
case self::AMOUNT_SHORTAGE:
$statusComment = "支払いステータスチェック請求金額より授受金額が少ないです。決済トランザクションID{$settlement->settlement_transaction_id}";
break;
case self::AMOUNT_EXCESS:
$statusComment = "支払いステータスチェック請求金額より授受金額が多いです。決済トランザクションID{$settlement->settlement_transaction_id}";
break;
default:
$statusComment = "支払いステータスチェック処理完了決済トランザクションID{$settlement->settlement_transaction_id}";
}
} else {
// その他のケース(対象なし、登録済み等)
$statusComment = "支払いステータスチェック処理完了決済トランザクションID{$settlement->settlement_transaction_id}";
}
// メール送信結果をバッチコメントに追加
if ($mailCommentSuffix) {
$statusComment .= $mailCommentSuffix;
}
// 重複判定用マーカー追加checkAlreadyProcessed/ShjFourBCheckCommand互換
$statusComment .= " settlement_transaction_id:{$settlement->settlement_transaction_id}";
// SHJ-8サービス呼び出し仕様JOB6: success/error動的判定
$this->shjEightService->execute(
$deviceId,
'SHJ-4B',
'SHJ-4支払いステータスチェック',
$isSuccess ? 'success' : 'error',
$statusComment,
$today,
$today
);
Log::info('SHJ-4B バッチ処理ログ作成完了', [
'settlement_transaction_id' => $settlement->settlement_transaction_id,
'status_comment' => $statusComment,
]);
} catch (\Exception $e) {
Log::error('SHJ-4B バッチ処理ログ作成エラー', [
'error' => $e->getMessage(),
'settlement_transaction_id' => $settlement->settlement_transaction_id,
]);
}
}
}