feat(billing): renewals, coupons, VAT numbering, billing cycles and invoice payment tracking
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
ea9a53f742
commit
25f23d541e
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
use App\Models\ActivityLog;
|
||||
use App\Models\Service;
|
||||
use Carbon\Carbon;
|
||||
|
||||
final class RenewalPeriodGuard
|
||||
{
|
||||
public static function violation(Service $service, int $months): ?string
|
||||
{
|
||||
$start = $service->expires_at && $service->expires_at->isFuture() ? $service->expires_at->copy() : now();
|
||||
$end = $start->copy()->addMonths($months);
|
||||
|
||||
if ($end->lte(now())) {
|
||||
return 'Không thể gia hạn cho khoảng thời gian trong quá khứ.';
|
||||
}
|
||||
|
||||
$overlapping = ActivityLog::query()
|
||||
->where('subject_type', Service::class)
|
||||
->where('subject_id', $service->id)
|
||||
->where('event', 'renewed')
|
||||
->get(['old_values', 'new_values'])
|
||||
->first(fn (ActivityLog $log) => self::overlaps($log, $start, $end));
|
||||
|
||||
if (! $overlapping) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'Khoảng thời gian %s – %s đã được gia hạn trước đó, không thể gia hạn trùng.',
|
||||
$start->format('d/m/Y'),
|
||||
$end->format('d/m/Y'),
|
||||
);
|
||||
}
|
||||
|
||||
private static function overlaps(ActivityLog $log, Carbon $start, Carbon $end): bool
|
||||
{
|
||||
$loggedEnd = $log->new_values['expires_at'] ?? null;
|
||||
if (! $loggedEnd) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$loggedEnd = Carbon::parse($loggedEnd);
|
||||
$loggedStart = ($log->old_values['expires_at'] ?? null)
|
||||
? Carbon::parse($log->old_values['expires_at'])
|
||||
: $loggedEnd->copy()->subMonths((int) ($log->new_values['months'] ?? 1));
|
||||
|
||||
return $loggedStart->lt($end) && $loggedEnd->gt($start);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user