| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273 |
- <?php
- namespace App\Services;
- use App\Models\ActivityReward;
- use App\Models\ActivityRewardRecipient;
- use Illuminate\Support\Facades\DB;
- class ActivityQuotaService
- {
- /** A preliminary check; award() always checks again under the activity lock. */
- public static function canAward(ActivityReward $activity, $memberId): bool
- {
- if ((int)$activity->type === 2) return true;
- if (!$activity->quota_enabled) {
- return $activity->budget === null || bccomp((string)($activity->budget_used ?? '0'), (string)$activity->budget, 2) < 0;
- }
- $member = static::memberKey($memberId);
- if ($member === null) return false;
- if (static::hasReceived($activity->id, $member)) return true;
- return $activity->reward_quota === null || (int)$activity->rewarded_count < (int)$activity->reward_quota;
- }
- /** Count a member only on their first successful positive reward for this activity. */
- public static function award(ActivityReward $activity, $memberId, $amount, callable $grant): bool
- {
- $member = static::memberKey($memberId);
- if ($member === null || !is_numeric($amount)
- || !preg_match('/^\d{1,12}(\.\d{1,2})?$/', (string)$amount)
- || bccomp((string)$amount, '0', 2) <= 0) {
- return false;
- }
- return DB::transaction(function () use ($activity, $member, $amount, $grant) {
- $locked = ActivityReward::where('id', $activity->id)->lockForUpdate()->first();
- if (!$locked) return false;
- if ((int)$locked->type === 2) {
- $grant();
- return true;
- }
- if (!$locked->quota_enabled) return ActivityBudgetService::award($locked, $amount, $grant);
- // Match the locked counter with a current read, even inside an older caller snapshot.
- $received = static::hasReceived($locked->id, $member, true);
- if (!$received && $locked->reward_quota !== null
- && (int)$locked->rewarded_count >= (int)$locked->reward_quota) {
- return false;
- }
- $grant();
- if (!$received) {
- ActivityRewardRecipient::create([
- 'activity_id' => $locked->id,
- 'member_id' => $member,
- 'created_at' => date('Y-m-d H:i:s'),
- ]);
- $locked->rewarded_count = (int)$locked->rewarded_count + 1;
- $locked->save();
- }
- return true;
- });
- }
- private static function hasReceived($activityId, string $memberId, bool $currentRead = false): bool
- {
- $query = ActivityRewardRecipient::where('activity_id', $activityId)->where('member_id', $memberId);
- return $currentRead ? $query->lockForUpdate()->first() !== null : $query->exists();
- }
- private static function memberKey($memberId): ?string
- {
- if (!is_int($memberId) && !is_string($memberId)) return null;
- $member = trim((string)$memberId);
- return $member === '' || $member === '0' || strlen($member) > 64 ? null : $member;
- }
- }
|