Files
ladill-transfer/app/Models/Transfer.php
T
isaaccladandCursor 311b5b40bb
Deploy Ladill Transfer / deploy (push) Successful in 51s
Bill transfer storage monthly with a 15-day grace period before deletion.
Files stay available while wallet renewals succeed each month. Failed renewals
keep files for TRANSFER_GRACE_PERIOD_DAYS (default 15) before automatic cleanup.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 12:28:33 +00:00

153 lines
3.7 KiB
PHP

<?php
namespace App\Models;
use Carbon\CarbonInterface;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Transfer extends Model
{
public const STATUS_ACTIVE = 'active';
public const STATUS_GRACE = 'grace';
public const STATUS_EXPIRED = 'expired';
public const STATUS_DELETED = 'deleted';
protected $fillable = [
'user_id',
'qr_code_id',
'title',
'message',
'password_hash',
'retention_days',
'expires_at',
'paid_until',
'grace_ends_at',
'last_billed_at',
'status',
'downloads_total',
'storage_bytes',
];
protected $casts = [
'retention_days' => 'integer',
'expires_at' => 'datetime',
'paid_until' => 'datetime',
'grace_ends_at' => 'datetime',
'last_billed_at' => 'datetime',
'downloads_total' => 'integer',
'storage_bytes' => 'integer',
];
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function qrCode(): BelongsTo
{
return $this->belongsTo(QrCode::class);
}
public function files(): HasMany
{
return $this->hasMany(TransferFile::class);
}
public function downloadEvents(): HasMany
{
return $this->hasMany(TransferDownloadEvent::class);
}
/** Files are available to recipients while paid or within the unpaid grace window. */
public function isAccessible(): bool
{
if ($this->status === self::STATUS_DELETED) {
return false;
}
if ($this->isPaid()) {
return true;
}
return $this->isInGracePeriod();
}
/** @deprecated Use isAccessible() — kept for existing call sites. */
public function isActive(): bool
{
return $this->isAccessible();
}
public function isPaid(): bool
{
if (! in_array($this->status, [self::STATUS_ACTIVE, self::STATUS_GRACE], true)) {
return false;
}
return $this->paid_until instanceof CarbonInterface && $this->paid_until->isFuture();
}
public function isInGracePeriod(): bool
{
if ($this->status !== self::STATUS_GRACE) {
return false;
}
return $this->grace_ends_at instanceof CarbonInterface && $this->grace_ends_at->isFuture();
}
public function isPastGracePeriod(): bool
{
return $this->grace_ends_at instanceof CarbonInterface && $this->grace_ends_at->isPast();
}
public function billingPeriodEndsAt(): ?CarbonInterface
{
return $this->paid_until;
}
public function isPasswordProtected(): bool
{
return $this->password_hash !== null && $this->password_hash !== '';
}
public function storageGb(): float
{
return round($this->storage_bytes / (1024 * 1024 * 1024), 3);
}
public function monthlyCostGhs(): float
{
return round($this->storageGb() * (float) config('transfer.price_per_gb_month', 0.30), 2);
}
public function billingStatusLabel(): string
{
if ($this->isPaid()) {
return 'Active';
}
if ($this->isInGracePeriod()) {
return 'Payment due';
}
return 'Unavailable';
}
public function scopeAccessible(Builder $query): Builder
{
return $query
->whereIn('status', [self::STATUS_ACTIVE, self::STATUS_GRACE])
->where(function (Builder $inner) {
$inner->where('paid_until', '>', now())
->orWhere('grace_ends_at', '>', now());
});
}
}