feat: add idempotency support for settlement adjustments and payments, fix rebate handling
Some checks failed
lotterLaravel CI / test (push) Has been cancelled
lotterLaravel E2E / e2e-api (push) Has been cancelled

- Added idempotency_key parameter to settlement bill adjustment and payment endpoints to prevent duplicate operations
- Fixed player rebate profile updates to preserve existing values when only one field is modified
- Moved wallet player validation earlier in AdminPlayerStoreController to prevent unnecessary processing
- Enhanced extra rebate calculation to include 'in_bill' and 'settled' statuses in AgentP
This commit is contained in:
2026-06-23 16:49:57 +08:00
parent 9a7522e928
commit 3c57941895
20 changed files with 734 additions and 127 deletions

View File

@@ -46,57 +46,59 @@ final class AgentSettlementPeriodCloseService
(string) $period->period_end,
);
try {
$aggregate = $this->aggregator->aggregate($adminSiteId, $periodStart, $periodEnd);
} catch (\InvalidArgumentException $e) {
if (str_starts_with($e->getMessage(), 'share_snapshot_missing')) {
throw ValidationException::withMessages([
'period' => ['share_snapshot_missing'],
]);
return DB::transaction(function () use ($periodId, $period, $adminSiteId, $periodStart, $periodEnd): array {
try {
$aggregate = $this->aggregator->aggregate($adminSiteId, $periodStart, $periodEnd);
} catch (\InvalidArgumentException $e) {
if (str_starts_with($e->getMessage(), 'share_snapshot_missing')) {
throw ValidationException::withMessages([
'period' => ['share_snapshot_missing'],
]);
}
throw $e;
}
throw $e;
}
$billIds = $this->billGenerator->generate($periodId, $adminSiteId, $aggregate);
$billIds = $this->billGenerator->generate($periodId, $adminSiteId, $aggregate);
$roundingDiff = $this->platformRounding->apply($periodId, $aggregate);
$roundingDiff = $this->platformRounding->apply($periodId, $aggregate);
$rebateStats = $this->periodCloseRebate->dispatchAndAllocate($periodId, $adminSiteId, $periodStart, $periodEnd);
$rebateStats = $this->periodCloseRebate->dispatchAndAllocate($periodId, $periodStart, $periodEnd);
$unsettled = $this->unsettledWarning->countForSite($adminSiteId, $periodStart, $periodEnd);
$unsettled = $this->unsettledWarning->countForSite($adminSiteId, $periodStart, $periodEnd);
DB::table('settlement_periods')->where('id', $periodId)->update([
'status' => 'closed',
'updated_at' => now(),
]);
DB::table('settlement_periods')->where('id', $periodId)->update([
'status' => 'closed',
'updated_at' => now(),
]);
$siteCode = (string) DB::table('admin_sites')->where('id', $adminSiteId)->value('code');
$siteCode = (string) DB::table('admin_sites')->where('id', $adminSiteId)->value('code');
DB::table('share_ledger')
->whereIn('id', function ($query) use ($siteCode, $periodStart, $periodEnd): void {
$query->select('sl.id')
->from('share_ledger as sl')
->join('players as p', 'p.id', '=', 'sl.player_id')
->where('p.site_code', $siteCode)
->whereNull('sl.settlement_period_id')
->whereBetween('sl.settled_at', [$periodStart, $periodEnd]);
})
->update(['settlement_period_id' => $periodId]);
DB::table('share_ledger')
->whereIn('id', function ($query) use ($siteCode, $periodStart, $periodEnd): void {
$query->select('sl.id')
->from('share_ledger as sl')
->join('players as p', 'p.id', '=', 'sl.player_id')
->where('p.site_code', $siteCode)
->whereNull('sl.settlement_period_id')
->whereBetween('sl.settled_at', [$periodStart, $periodEnd]);
})
->update(['settlement_period_id' => $periodId]);
$this->reconcileAllocatedCreditForSite($adminSiteId);
$this->reconcileAllocatedCreditForSite($adminSiteId);
return [
'period_id' => $periodId,
'bill_ids' => $billIds,
'player_count' => count($aggregate['players']),
'agent_edges' => $aggregate['agent_edges'],
'rebate_dispatched' => $rebateStats['dispatched'],
'rebate_allocations' => $rebateStats['allocations'],
'unsettled_ticket_count' => $unsettled['count'],
'unsettled_ticket_sample' => $unsettled['ticket_item_ids'],
'platform_rounding_adjustment' => $roundingDiff,
];
return [
'period_id' => $periodId,
'bill_ids' => $billIds,
'player_count' => count($aggregate['players']),
'agent_edges' => $aggregate['agent_edges'],
'rebate_dispatched' => $rebateStats['dispatched'],
'rebate_allocations' => $rebateStats['allocations'],
'unsettled_ticket_count' => $unsettled['count'],
'unsettled_ticket_sample' => $unsettled['ticket_item_ids'],
'platform_rounding_adjustment' => $roundingDiff,
];
});
}
/** 关账后按真理源重算各代理「已下发额度」,避免与直属玩家/下级代理授信脱节。 */