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:
baodev0
2026-10-19 16:37:56 +07:00
co-authored by Claude Sonnet 5.5
parent ea9a53f742
commit 25f23d541e
134 changed files with 4926 additions and 185 deletions
+21
View File
@@ -5,6 +5,7 @@ namespace App\Jobs\Mail;
use App\Mail\GenericTemplateMail;
use App\Mail\InvoiceNotificationMail;
use App\Models\Invoice;
use App\Models\InvoiceAttachment;
use App\Models\Mail as MailLog;
use App\Services\Email\EmailTemplateService;
use App\Services\InvoicePdfService;
@@ -31,6 +32,8 @@ class SendInvoiceMailJob implements ShouldQueue
public readonly ?string $subject,
public readonly ?string $html,
public readonly ?int $sentBy,
public readonly bool $attachVat = false,
public readonly array $contractIds = [],
) {}
public function handle(InvoicePdfService $pdfService, EmailTemplateService $templates): void
@@ -47,6 +50,7 @@ class SendInvoiceMailJob implements ShouldQueue
$mail = $this->html !== null
? $this->editedMail($templates, $pdf, $pdfFileName)
: InvoiceNotificationMail::forInvoice($invoice, $this->type, $this->message, $pdf, $pdfFileName);
$this->attachExtras($mail, $invoice, $pdfService);
$sent = Mail::to($invoice->customer->email)->send($mail);
$this->log($templates, $invoice, $mail, $sent === null ? 'failed' : 'sent');
} catch (Throwable $e) {
@@ -64,6 +68,23 @@ class SendInvoiceMailJob implements ShouldQueue
}
}
private function attachExtras(Mailable $mail, Invoice $invoice, InvoicePdfService $pdfService): void
{
if ($this->attachVat && $invoice->vat_number) {
$mail->attachData($pdfService->generate($invoice), $pdfService->vatFileName($invoice), ['mime' => 'application/pdf']);
}
$contracts = InvoiceAttachment::query()
->where('invoice_id', $invoice->id)
->where('type', InvoiceAttachment::TYPE_CONTRACT)
->whereIn('id', $this->contractIds)
->get();
foreach ($contracts as $contract) {
$mail->attachFromStorageDisk($contract->disk, $contract->path, $contract->filename, ['mime' => $contract->mime_type]);
}
}
private function editedMail(EmailTemplateService $templates, string $pdf, string $pdfFileName): GenericTemplateMail
{
return (new GenericTemplateMail(