| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283 |
- <?php
- namespace App\Console\Commands;
- use App\Services\Sharing\BetFactSynchronizer;
- use App\Services\Sharing\EffectiveBet;
- use Illuminate\Console\Command;
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Schema;
- class RecalculateShareRebates extends Command
- {
- protected $signature = 'share:recalculate-rebates {--write : Correct unpaid PC28 daily bases; default previews all history}';
- protected $description = 'Recalculate unpaid PC28 rebate bases without paying any wallet or changing paid rows';
- public function handle(BetFactSynchronizer $synchronizer): int
- {
- $result = $this->recalculate($synchronizer, (bool) $this->option('write'));
- $this->line(json_encode($result, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
- return $result['skipped'] === 0 ? self::SUCCESS : self::FAILURE;
- }
- public function recalculate(BetFactSynchronizer $synchronizer, bool $write = false): array
- {
- $result = ['write' => $write, 'reviewed' => 0, 'changed' => 0, 'unchanged' => 0, 'skipped' => 0, 'paid_untouched' => 0, 'rows' => []];
- if (!Schema::hasTable('rebates') || !Schema::hasTable('lhc_order') || !Schema::hasTable('lhc_order_item')) {
- throw new \RuntimeException('Rebate and current lottery source tables are required');
- }
- $groups = [];
- $undatedMembers = [];
- DB::table('lhc_order')->whereIn('type', [5, 6])->orderBy('id')->chunkById(200, function ($orders) use ($synchronizer, &$groups, &$undatedMembers) {
- foreach ($orders as $order) {
- $member = (string) $order->member_id;
- try { $date = EffectiveBet::newYorkDate($order->created_at ?? null); }
- catch (\Throwable $e) { $undatedMembers[$member] = 'An original betting date is missing or invalid'; continue; }
- $key = $member . '\0' . $date;
- $groups[$key] ??= ['total' => EffectiveBet::zero(), 'effective' => EffectiveBet::zero(), 'errors' => []];
- try {
- $snapshot = $synchronizer->lotteryOrder($order);
- if ($snapshot['facts'] === []) throw new \RuntimeException('Source member is demo or unavailable');
- if ($snapshot['unsettled']) throw new \RuntimeException('Original betting day still has unfinished bets');
- $groups[$key]['total'] = bcadd($groups[$key]['total'], $snapshot['total_bet'], EffectiveBet::SCALE);
- foreach ($snapshot['facts'] as $fact) {
- $groups[$key]['effective'] = bcadd($groups[$key]['effective'], $fact['effective_amount'], EffectiveBet::SCALE);
- }
- } catch (\Throwable $e) { $groups[$key]['errors'][] = 'order ' . $order->id . ': ' . $e->getMessage(); }
- }
- });
- $result['paid_untouched'] = DB::table('rebates')->where('status', 1)->count();
- DB::table('rebates')->where('status', 0)->orderBy('id')->chunkById(200, function ($rebates) use ($write, $groups, $undatedMembers, &$result) {
- foreach ($rebates as $rebate) {
- $result['reviewed']++;
- $key = (string) $rebate->member_id . '\0' . (string) $rebate->date;
- $group = $groups[$key] ?? null;
- $reason = $undatedMembers[(string) $rebate->member_id] ?? null;
- if ($reason === null && $group === null) $reason = 'No original-date source; legacy date cannot be inferred';
- if ($reason === null && $group['errors'] !== []) $reason = implode('; ', $group['errors']);
- if ($reason === null && bccomp($group['total'], (string) $rebate->betting_amount, 2) !== 0) {
- $reason = 'Original-date stake total differs from stored betting_amount; date/refund history needs review';
- }
- $row = ['id' => $rebate->id, 'member_id' => (string) $rebate->member_id, 'date' => $rebate->date,
- 'before' => bcadd((string) $rebate->effective_betting_amount, '0', 2), 'after' => null, 'status' => 'skipped', 'reason' => $reason];
- if ($reason !== null) { $result['skipped']++; $result['rows'][] = $row; continue; }
- $row['after'] = bcadd($group['effective'], '0', 2);
- if (bccomp($row['before'], $row['after'], 2) === 0) {
- $row['status'] = 'unchanged'; $result['unchanged']++;
- } elseif (!$write) {
- $row['status'] = 'would_change'; $result['changed']++;
- } else {
- // Compare-and-set keeps a concurrent settlement/payment from being overwritten.
- $updated = DB::table('rebates')->where('id', $rebate->id)->where('status', 0)
- ->where('betting_amount', $rebate->betting_amount)
- ->where('effective_betting_amount', $rebate->effective_betting_amount)
- ->update(['effective_betting_amount' => $row['after']]);
- if ($updated === 1) { $row['status'] = 'changed'; $result['changed']++; }
- else { $row['status'] = 'skipped'; $row['reason'] = 'Rebate changed during recalculation; retry preview'; $result['skipped']++; }
- }
- $result['rows'][] = $row;
- }
- });
- return $result;
- }
- }
|