Files
ladill-transfer/app/Models/Transfer.php
T
isaaccladandCursor 7c4673adb6
Deploy Ladill Transfer / deploy (push) Successful in 44s
Add recipient email and milestone notifications on transfer create.
Recipients can be emailed the download link immediately and at optional
reminders (7/3/1 days before unavailable, or when grace period starts).

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

178 lines
4.6 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',
'recipient_email',
'recipient_email_milestones',
'recipient_milestones_sent',
'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',
'recipient_email_milestones' => 'array',
'recipient_milestones_sent' => 'array',
'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());
});
}
public function wantsRecipientMilestone(string $milestone): bool
{
return in_array($milestone, (array) ($this->recipient_email_milestones ?? []), true);
}
public function recipientMilestoneSent(string $milestone): bool
{
return in_array($milestone, (array) ($this->recipient_milestones_sent ?? []), true);
}
public function markRecipientMilestoneSent(string $milestone): void
{
$sent = (array) ($this->recipient_milestones_sent ?? []);
$sent[] = $milestone;
$this->forceFill([
'recipient_milestones_sent' => array_values(array_unique($sent)),
])->save();
}
}