feat: 切换 schema dump 基线并增强返点结算与管理校验

This commit is contained in:
2026-06-08 17:41:41 +08:00
parent 2d32f006c5
commit 8d5d7f5b17
130 changed files with 5746 additions and 6723 deletions

View File

@@ -0,0 +1,58 @@
<?php
namespace App\Services\Ticket;
use App\Models\AgentProfile;
use App\Models\Player;
use Illuminate\Support\Facades\DB;
final class InstantRebateResolver
{
/**
* @return array{
* base_rebate_rate: float,
* player_addon_rebate_rate: float,
* final_rebate_rate: float,
* inherited_from_agent: bool
* }
*/
public function resolveForPlayer(Player $player, string $playCode, float $baseRebateRate): array
{
$row = DB::table('player_rebate_profiles')
->where('player_id', $player->id)
->where('game_type', $playCode)
->first();
if ($row === null) {
$row = DB::table('player_rebate_profiles')
->where('player_id', $player->id)
->where('game_type', '*')
->first();
}
$addonRate = 0.0;
$inheritedFromAgent = false;
if ($row !== null && ! (bool) $row->inherit_from_agent) {
$addonRate = (float) $row->rebate_rate + (float) $row->extra_rebate_rate;
} else {
$inheritedFromAgent = true;
$profile = $player->agent_node_id
? AgentProfile::query()->where('agent_node_id', $player->agent_node_id)->first()
: null;
$addonRate = (float) ($profile?->default_player_rebate ?? 0);
}
return [
'base_rebate_rate' => $this->normalizeRate($baseRebateRate),
'player_addon_rebate_rate' => $this->normalizeRate($addonRate),
'final_rebate_rate' => $this->normalizeRate($baseRebateRate + $addonRate),
'inherited_from_agent' => $inheritedFromAgent,
];
}
private function normalizeRate(float $rate): float
{
return max(0.0, min(1.0, $rate));
}
}

View File

@@ -24,6 +24,7 @@ final class TicketPlacementService
public function __construct(
private readonly PlayCatalogResolver $catalogResolver,
private readonly PlayRuleEngine $ruleEngine,
private readonly InstantRebateResolver $instantRebateResolver,
private readonly RiskPoolService $riskPoolService,
private readonly TicketWalletService $ticketWalletService,
private readonly JackpotContributionService $jackpotContribution,
@@ -532,12 +533,29 @@ final class TicketPlacementService
*/
private function applyCreditLineInstantRebatePolicy(Player $player, array $evaluated): array
{
if (! PlayerFundingMode::usesCredit($player)) {
$resolved = $this->instantRebateResolver->resolveForPlayer(
$player,
(string) $evaluated['play_code'],
(float) $evaluated['rebate_rate_snapshot'],
);
$evaluated['rule_snapshot_json']['base_rebate_rate'] = number_format($resolved['base_rebate_rate'], 4, '.', '');
$evaluated['rule_snapshot_json']['player_addon_rebate_rate'] = number_format($resolved['player_addon_rebate_rate'], 4, '.', '');
$evaluated['rule_snapshot_json']['rebate_inherited_from_agent'] = $resolved['inherited_from_agent'];
if (PlayerFundingMode::usesCredit($player)) {
$evaluated['rebate_rate_snapshot'] = '0.0000';
$evaluated['actual_deduct_amount'] = (int) $evaluated['total_bet_amount'];
return $evaluated;
}
$evaluated['rebate_rate_snapshot'] = '0.0000';
$evaluated['actual_deduct_amount'] = (int) $evaluated['total_bet_amount'];
$finalRate = $resolved['final_rebate_rate'];
$evaluated['rebate_rate_snapshot'] = number_format($finalRate, 4, '.', '');
$evaluated['actual_deduct_amount'] = max(
0,
(int) floor((int) $evaluated['total_bet_amount'] * (1 - $finalRate)),
);
return $evaluated;
}

View File

@@ -3,15 +3,18 @@
namespace App\Services\Ticket;
use App\Models\Draw;
use App\Models\Player;
use App\Lottery\ErrorCode;
use App\Exceptions\TicketOperationException;
use App\Services\Draw\DrawHallSnapshotBuilder;
use App\Support\PlayerFundingMode;
final class TicketPreviewService
{
public function __construct(
private readonly PlayCatalogResolver $catalogResolver,
private readonly PlayRuleEngine $ruleEngine,
private readonly InstantRebateResolver $instantRebateResolver,
private readonly RiskPoolService $riskPoolService,
private readonly DrawHallSnapshotBuilder $drawHallSnapshot,
) {}
@@ -20,7 +23,7 @@ final class TicketPreviewService
* @param array<string, mixed> $payload
* @return array<string, mixed>
*/
public function preview(array $payload): array
public function preview(Player $player, array $payload): array
{
$drawNo = trim((string) ($payload['draw_id'] ?? ''));
$draw = $drawNo === ''
@@ -62,6 +65,7 @@ final class TicketPreviewService
$resolved['play_config'],
$resolved['odds_items'],
);
$evaluated = $this->applyPlayerInstantRebate($player, $evaluated);
$locks = array_map(fn (array $combo): array => [
'number_4d' => $combo['number_4d'],
@@ -131,4 +135,37 @@ final class TicketPreviewService
'warnings' => $warningRows,
];
}
/**
* @param array<string, mixed> $evaluated
* @return array<string, mixed>
*/
private function applyPlayerInstantRebate(Player $player, array $evaluated): array
{
$resolved = $this->instantRebateResolver->resolveForPlayer(
$player,
(string) $evaluated['play_code'],
(float) $evaluated['rebate_rate_snapshot'],
);
$evaluated['rule_snapshot_json']['base_rebate_rate'] = number_format($resolved['base_rebate_rate'], 4, '.', '');
$evaluated['rule_snapshot_json']['player_addon_rebate_rate'] = number_format($resolved['player_addon_rebate_rate'], 4, '.', '');
$evaluated['rule_snapshot_json']['rebate_inherited_from_agent'] = $resolved['inherited_from_agent'];
if (PlayerFundingMode::usesCredit($player)) {
$evaluated['rebate_rate_snapshot'] = '0.0000';
$evaluated['actual_deduct_amount'] = (int) $evaluated['total_bet_amount'];
return $evaluated;
}
$finalRate = $resolved['final_rebate_rate'];
$evaluated['rebate_rate_snapshot'] = number_format($finalRate, 4, '.', '');
$evaluated['actual_deduct_amount'] = max(
0,
(int) floor((int) $evaluated['total_bet_amount'] * (1 - $finalRate)),
);
return $evaluated;
}
}