php-commerce/Models/ReferralCode.php
Snider 8f27fe85c3 refactor: update Tenant module imports after namespace migration
Updates all references from Core\Mod\Tenant to Core\Tenant following
the monorepo separation. The Tenant module now lives in its own package
with the simplified namespace.

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

216 lines
5.1 KiB
PHP

<?php
declare(strict_types=1);
namespace Core\Mod\Commerce\Models;
use Core\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}");
}
}