diff --git a/app/Console/Commands/SendPaymentReminders.php b/app/Console/Commands/SendPaymentReminders.php new file mode 100644 index 00000000..1306e18b --- /dev/null +++ b/app/Console/Commands/SendPaymentReminders.php @@ -0,0 +1,51 @@ + + +is_active) { + $this->info('Reminder system is not active'); + return; + } + + $overdueInvoices = Invoice::where('payment_status', 'pending') + ->where('invoice_date', '<=', Carbon::now()->subDays($settings->days_before_reminder)) + ->where(function ($query) use ($settings) { + $query->where('reminders_sent', '<', $settings->max_reminders) + ->orWhereNull('last_reminder_sent_at'); + }) + ->get(); + + foreach ($overdueInvoices as $invoice) { + if ($invoice->customer && $invoice->customer->email) { + if (!$invoice->last_reminder_sent_at || + Carbon::parse($invoice->last_reminder_sent_at)->addDays($settings->reminder_frequency_days)->isPast()) { + + $invoice->customer->notify(new PaymentReminderNotification($invoice)); + + $invoice->reminders_sent++; + $invoice->last_reminder_sent_at = now(); + $invoice->save(); + + $this->info("Reminder sent for Invoice #{$invoice->invoice_id}"); + } + } + } + } +} \ No newline at end of file diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index dcdc363b..5f869a76 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -36,9 +36,13 @@ protected function commands(): void class Kernel extends ConsoleKernel { + protected $commands = [ + Commands\SendPaymentReminders::class, + ]; + protected function schedule(Schedule $schedule) { - $schedule->command('invoices:calculate-late-fees')->daily(); + $schedule->command('invoices:send-reminders')->daily(); } protected function commands() diff --git a/app/Filament/App/Resources/ReminderSettingResource.php b/app/Filament/App/Resources/ReminderSettingResource.php new file mode 100644 index 00000000..d10ef2e1 --- /dev/null +++ b/app/Filament/App/Resources/ReminderSettingResource.php @@ -0,0 +1,84 @@ + + +schema([ + TextInput::make('days_before_reminder') + ->label('Days Before First Reminder') + ->numeric() + ->required() + ->min(1) + ->helperText('Number of days after invoice date before sending first reminder'), + + TextInput::make('reminder_frequency_days') + ->label('Days Between Reminders') + ->numeric() + ->required() + ->min(1) + ->helperText('Number of days to wait between reminders'), + + TextInput::make('max_reminders') + ->label('Maximum Number of Reminders') + ->numeric() + ->required() + ->min(1) + ->helperText('Maximum number of reminders to send per invoice'), + + Toggle::make('is_active') + ->label('Enable Reminders') + ->required() + ->helperText('Toggle the reminder system on/off'), + ]); + } + + public static function table(Table $table): Table + { + return $table + ->columns([ + TextColumn::make('days_before_reminder') + ->label('Days Before First Reminder') + ->sortable(), + TextColumn::make('reminder_frequency_days') + ->label('Reminder Frequency (Days)') + ->sortable(), + TextColumn::make('max_reminders') + ->label('Max Reminders') + ->sortable(), + ToggleColumn::make('is_active') + ->label('Active'), + ]); + } + + public static function getPages(): array + { + return [ + 'index' => ListRecords::route('/'), + 'edit' => EditRecord::route('/{record}/edit'), + ]; + } +} \ No newline at end of file diff --git a/app/Models/ReminderSetting.php b/app/Models/ReminderSetting.php new file mode 100644 index 00000000..b7ae7ab6 --- /dev/null +++ b/app/Models/ReminderSetting.php @@ -0,0 +1,24 @@ + + + 'boolean', + ]; +} \ No newline at end of file diff --git a/app/Notifications/PaymentReminderNotification.php b/app/Notifications/PaymentReminderNotification.php new file mode 100644 index 00000000..815a4079 --- /dev/null +++ b/app/Notifications/PaymentReminderNotification.php @@ -0,0 +1,40 @@ + + +invoice = $invoice; + } + + public function via($notifiable): array + { + return ['mail']; + } + + public function toMail($notifiable): MailMessage + { + return (new MailMessage) + ->subject('Payment Reminder - Invoice #' . $this->invoice->invoice_id) + ->greeting('Hello ' . $this->invoice->customer->customer_name) + ->line('This is a reminder that payment for Invoice #' . $this->invoice->invoice_id . ' is overdue.') + ->line('Amount due: $' . number_format($this->invoice->getTotalWithTax(), 2)) + ->line('Due date: ' . $this->invoice->invoice_date->format('Y-m-d')) + ->action('View Invoice', url('/invoices/' . $this->invoice->invoice_id)) + ->line('Thank you for your business!'); + } +} \ No newline at end of file diff --git a/database/migrations/2024_02_24_000000_create_reminder_settings_table.php b/database/migrations/2024_02_24_000000_create_reminder_settings_table.php new file mode 100644 index 00000000..f995a842 --- /dev/null +++ b/database/migrations/2024_02_24_000000_create_reminder_settings_table.php @@ -0,0 +1,27 @@ + + +id(); + $table->integer('days_before_reminder')->default(3); + $table->integer('reminder_frequency_days')->default(7); + $table->integer('max_reminders')->default(3); + $table->boolean('is_active')->default(true); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('reminder_settings'); + } +}; \ No newline at end of file diff --git a/database/migrations/2024_02_24_000001_add_reminder_columns_to_invoices.php b/database/migrations/2024_02_24_000001_add_reminder_columns_to_invoices.php new file mode 100644 index 00000000..96b04d37 --- /dev/null +++ b/database/migrations/2024_02_24_000001_add_reminder_columns_to_invoices.php @@ -0,0 +1,25 @@ + + +integer('reminders_sent')->default(0); + $table->timestamp('last_reminder_sent_at')->nullable(); + }); + } + + public function down(): void + { + Schema::table('invoices', function (Blueprint $table) { + $table->dropColumn(['reminders_sent', 'last_reminder_sent_at']); + }); + } +}; \ No newline at end of file