*/ public function apply( SettlementBatch $batch, AdminUser $admin, int $playerId, int $amountDelta, string $reason, Request $request, ): array { return DB::transaction(function () use ($batch, $admin, $playerId, $amountDelta, $reason, $request): array { /** @var SettlementBatch $lockedBatch */ $lockedBatch = SettlementBatch::query()->whereKey($batch->id)->lockForUpdate()->firstOrFail(); if ($lockedBatch->status !== SettlementBatchStatus::Paid->value) { throw new \RuntimeException('settlement_batch_not_paid'); } $detail = TicketSettlementDetail::query() ->where('settlement_batch_id', $lockedBatch->id) ->whereHas('ticketItem', fn ($query) => $query->where('player_id', $playerId)) ->with(['ticketItem.order']) ->orderBy('id') ->first(); if ($detail === null || $detail->ticketItem === null) { throw new \RuntimeException('settlement_player_not_in_batch'); } $player = Player::query()->whereKey($playerId)->firstOrFail(); $currencyCode = strtoupper((string) ($detail->ticketItem->order?->currency_code ?? 'NPR')); $correctionNo = $this->newCorrectionNo(); $remark = sprintf( 'settlement_batch_adjustment:%d:%d:%s', (int) $lockedBatch->id, $playerId, mb_substr(trim($reason), 0, 180) ); $before = [ 'batch_id' => (int) $lockedBatch->id, 'batch_status' => (string) $lockedBatch->status, 'player_id' => $playerId, 'currency_code' => $currencyCode, 'amount_delta' => $amountDelta, 'reason' => $reason, 'ticket_item_id' => (int) $detail->ticket_item_id, 'ticket_no' => (string) ($detail->ticketItem->ticket_no ?? ''), 'original_win_amount' => (int) $detail->win_amount, 'original_jackpot_allocation_amount' => (int) $detail->jackpot_allocation_amount, ]; $txnNo = $this->walletService->applySettlementCorrection( $player, $currencyCode, $amountDelta, $correctionNo, $remark, ); AuditLogger::recordForAdmin( $admin, $request, moduleCode: 'settlement', actionCode: 'payout_adjustment', targetType: 'settlement_batch_adjustment', targetId: $correctionNo, beforeJson: $before, afterJson: [ 'batch_id' => (int) $lockedBatch->id, 'player_id' => $playerId, 'currency_code' => $currencyCode, 'amount_delta' => $amountDelta, 'direction' => $amountDelta > 0 ? 'credit' : 'debit', 'reason' => $reason, 'correction_no' => $correctionNo, 'wallet_txn_no' => $txnNo, ], ); $request->attributes->set(RecordAdminApiAudit::ATTRIBUTE_AUDIT_RECORDED, true); return [ 'batch_id' => (int) $lockedBatch->id, 'player_id' => $playerId, 'currency_code' => $currencyCode, 'amount_delta' => $amountDelta, 'direction' => $amountDelta > 0 ? 'credit' : 'debit', 'reason' => $reason, 'correction_no' => $correctionNo, 'wallet_txn_no' => $txnNo, ]; }); } private function newCorrectionNo(): string { return 'SC'.now()->format('YmdHis').str_pad((string) random_int(0, 999999), 6, '0', STR_PAD_LEFT); } }