billGuard->markConfirmed($billId); } /** * @param array{method?: string|null, proof?: string|null, remark?: string|null} $meta */ public function recordPayment(int $billId, int $amount, int $adminUserId, array $meta = []): void { $bill = DB::table('settlement_bills')->where('id', $billId)->first(); if ($bill === null) { throw new \InvalidArgumentException('bill_not_found'); } $this->billGuard->assertPeriodMutable($billId); $amount = min($amount, (int) $bill->unpaid_amount); if ($amount <= 0) { return; } DB::table('payment_records')->insert([ 'settlement_bill_id' => $billId, 'payer_type' => (string) $bill->owner_type, 'payer_id' => (int) $bill->owner_id, 'payee_type' => (string) $bill->counterparty_type, 'payee_id' => (int) $bill->counterparty_id, 'amount' => $amount, 'method' => $meta['method'] ?? null, 'proof' => $meta['proof'] ?? null, 'remark' => $meta['remark'] ?? null, 'status' => 'confirmed', 'created_by' => $adminUserId, 'confirmed_by' => $adminUserId, 'confirmed_at' => now(), 'created_at' => now(), 'updated_at' => now(), ]); $newPaid = (int) $bill->paid_amount + $amount; $newUnpaid = max(0, (int) $bill->unpaid_amount - $amount); $status = $newUnpaid === 0 ? 'settled' : 'partial_paid'; DB::table('settlement_bills')->where('id', $billId)->update([ 'paid_amount' => $newPaid, 'unpaid_amount' => $newUnpaid, 'status' => $status, 'updated_at' => now(), ]); if ($bill->owner_type === 'player' && (int) $bill->owner_id > 0) { $player = Player::query()->find((int) $bill->owner_id); if ($player !== null) { $this->playerCreditService->releaseFromSettlement($player, $amount, $billId); if ($status === 'settled') { $this->periodCloseRebate->markRebatesSettledForBill($billId); } } } $this->periodCompletion->syncIfReady((int) $bill->settlement_period_id); } }