php-commerce/Models/ReferralCode.php
Snider a774f4e285 refactor: migrate namespace from Core\Commerce to Core\Mod\Commerce
Align commerce module with the monorepo module structure by updating
all namespaces to use the Core\Mod\Commerce convention. This change
supports the recent monorepo separation and ensures consistency with
other modules.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 16:23:12 +00:00

216 lines
5.1 KiB
PHP

<?php
declare(strict_types=1);
namespace Core\Mod\Commerce\Models;
use Core\Mod\Tenant\Models\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Spatie\Activitylog\LogOptions;
use Spatie\Activitylog\Traits\LogsActivity;
/**
* ReferralCode model for tracking referral/affiliate codes.
*
* Codes can be user-specific (from their namespace), campaign codes,
* or custom promotional codes with special commission rates.
*
* @property int $id
* @property string $code
* @property int|null $user_id
* @property string $type
* @property float|null $commission_rate
* @property int $cookie_days
* @property int|null $max_uses
* @property int $uses_count
* @property \Carbon\Carbon|null $valid_from
* @property \Carbon\Carbon|null $valid_until
* @property bool $is_active
* @property string|null $campaign_name
* @property array|null $metadata
*/
class ReferralCode extends Model
{
use LogsActivity;
protected $table = 'commerce_referral_codes';
// Code types
public const TYPE_USER = 'user'; // Auto-generated from user namespace
public const TYPE_CAMPAIGN = 'campaign'; // Marketing campaign codes
public const TYPE_CUSTOM = 'custom'; // Custom promotional codes
// Default attribution cookie duration (days)
public const DEFAULT_COOKIE_DAYS = 90;
protected $fillable = [
'code',
'user_id',
'type',
'commission_rate',
'cookie_days',
'max_uses',
'uses_count',
'valid_from',
'valid_until',
'is_active',
'campaign_name',
'metadata',
];
protected $casts = [
'commission_rate' => 'decimal:2',
'cookie_days' => 'integer',
'max_uses' => 'integer',
'uses_count' => 'integer',
'valid_from' => 'datetime',
'valid_until' => 'datetime',
'is_active' => 'boolean',
'metadata' => 'array',
];
// Relationships
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
// Validation
/**
* Check if code is currently valid for use.
*/
public function isValid(): bool
{
if (! $this->is_active) {
return false;
}
if ($this->valid_from && $this->valid_from->isFuture()) {
return false;
}
if ($this->valid_until && $this->valid_until->isPast()) {
return false;
}
if ($this->max_uses && $this->uses_count >= $this->max_uses) {
return false;
}
return true;
}
/**
* Check if code has reached max uses.
*/
public function hasReachedMaxUses(): bool
{
if ($this->max_uses === null) {
return false;
}
return $this->uses_count >= $this->max_uses;
}
// Getters
/**
* Get effective commission rate (own or default).
*/
public function getEffectiveCommissionRate(): float
{
return $this->commission_rate ?? ReferralCommission::DEFAULT_COMMISSION_RATE;
}
/**
* Get effective cookie duration in days.
*/
public function getEffectiveCookieDays(): int
{
return $this->cookie_days ?? self::DEFAULT_COOKIE_DAYS;
}
// Actions
/**
* Increment usage count.
*/
public function incrementUsage(): void
{
$this->increment('uses_count');
}
/**
* Activate code.
*/
public function activate(): void
{
$this->update(['is_active' => true]);
}
/**
* Deactivate code.
*/
public function deactivate(): void
{
$this->update(['is_active' => false]);
}
// Scopes
public function scopeActive($query)
{
return $query->where('is_active', true);
}
public function scopeValid($query)
{
return $query->active()
->where(function ($q) {
$q->whereNull('valid_from')
->orWhere('valid_from', '<=', now());
})
->where(function ($q) {
$q->whereNull('valid_until')
->orWhere('valid_until', '>=', now());
})
->where(function ($q) {
$q->whereNull('max_uses')
->orWhereRaw('uses_count < max_uses');
});
}
public function scopeByCode($query, string $code)
{
return $query->where('code', $code);
}
public function scopeForUser($query, int $userId)
{
return $query->where('user_id', $userId);
}
public function scopeByType($query, string $type)
{
return $query->where('type', $type);
}
public function scopeCampaign($query)
{
return $query->where('type', self::TYPE_CAMPAIGN);
}
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults()
->logOnly(['code', 'is_active', 'commission_rate', 'max_uses'])
->logOnlyDirty()
->dontSubmitEmptyLogs()
->setDescriptionForEvent(fn (string $eventName) => "Referral code {$eventName}");
}
}