Files
lotteryLaravel/app/Services/AgentSettlement/AgentSettlementBadDebtService.php

136 lines
4.9 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\AgentSettlement;
use App\Models\Player;
use App\Services\Player\PlayerCreditService;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;
/** 坏账核销:原账单保留,记 bad_debt 调整与归档单§2、§21.1)。 */
final class AgentSettlementBadDebtService
{
public function __construct(
private readonly AgentSettlementPeriodCompletionService $periodCompletion,
private readonly PlayerCreditService $playerCreditService,
) {}
public function writeOff(int $originalBillId, ?string $reason, int $adminUserId): int
{
$original = DB::table('settlement_bills')->where('id', $originalBillId)->first();
if ($original === null) {
throw new \InvalidArgumentException('bill_not_found');
}
if ($this->periodCompletion->isPeriodReadOnly((int) $original->settlement_period_id)) {
throw ValidationException::withMessages([
'period' => ['completed'],
]);
}
if (! in_array((string) $original->status, ['confirmed', 'partial_paid', 'overdue'], true)) {
throw ValidationException::withMessages([
'bill' => ['not_eligible'],
]);
}
$unpaid = (int) $original->unpaid_amount;
if ($unpaid <= 0) {
throw ValidationException::withMessages([
'bill' => ['no_unpaid'],
]);
}
if (in_array((string) $original->bill_type, ['adjustment', 'reversal', 'bad_debt'], true)) {
throw ValidationException::withMessages([
'bill' => ['not_eligible'],
]);
}
return (int) DB::transaction(function () use ($original, $originalBillId, $unpaid, $reason, $adminUserId): int {
$now = now();
$periodId = (int) $original->settlement_period_id;
$archiveBillId = (int) DB::table('settlement_bills')->insertGetId([
'settlement_period_id' => $periodId,
'bill_type' => 'bad_debt',
'owner_type' => (string) $original->owner_type,
'owner_id' => (int) $original->owner_id,
'counterparty_type' => (string) $original->counterparty_type,
'counterparty_id' => (int) $original->counterparty_id,
'gross_win_loss' => 0,
'rebate_amount' => 0,
'adjustment_amount' => -$unpaid,
'platform_rounding_adjustment' => 0,
'net_amount' => 0,
'paid_amount' => 0,
'unpaid_amount' => 0,
'status' => 'settled',
'reversed_bill_id' => $originalBillId,
'meta_json' => json_encode([
'original_bill_id' => $originalBillId,
'written_off_amount' => $unpaid,
'original_net_amount' => (int) $original->net_amount,
]),
'locked_at' => $now,
'confirmed_at' => $now,
'created_at' => $now,
'updated_at' => $now,
]);
DB::table('settlement_adjustments')->insert([
'settlement_period_id' => $periodId,
'original_bill_id' => $originalBillId,
'adjustment_type' => 'bad_debt',
'amount' => $unpaid,
'reason' => $reason,
'created_by' => $adminUserId > 0 ? $adminUserId : null,
'created_at' => $now,
'updated_at' => $now,
]);
DB::table('settlement_bills')->where('id', $originalBillId)->update([
'unpaid_amount' => 0,
'status' => 'settled',
'meta_json' => json_encode(array_merge(
$this->decodeMeta($original->meta_json),
[
'bad_debt_bill_id' => $archiveBillId,
'written_off_amount' => $unpaid,
],
)),
'updated_at' => $now,
]);
if ((string) $original->owner_type === 'player' && (int) $original->owner_id > 0 && (int) $original->net_amount > 0) {
$player = Player::query()->find((int) $original->owner_id);
if ($player !== null) {
$this->playerCreditService->releaseFromSettlement($player, $unpaid, $originalBillId);
}
}
$this->periodCompletion->syncIfReady($periodId);
return $archiveBillId;
});
}
/**
* @return array<string, mixed>
*/
private function decodeMeta(mixed $metaJson): array
{
if ($metaJson === null || $metaJson === '') {
return [];
}
if (is_array($metaJson)) {
return $metaJson;
}
$decoded = json_decode((string) $metaJson, true);
return is_array($decoded) ? $decoded : [];
}
}