Skip to content

Commit

Permalink
Merge pull request #360 from liberu-accounting/sweep/Implement-Paymen…
Browse files Browse the repository at this point in the history
…t-Reminder-System-for-Invoices

Implement Payment Reminder System for Invoices
  • Loading branch information
curtisdelicata authored Dec 24, 2024
2 parents 156d9d9 + 1043972 commit 319ec95
Show file tree
Hide file tree
Showing 7 changed files with 256 additions and 1 deletion.
51 changes: 51 additions & 0 deletions app/Console/Commands/SendPaymentReminders.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@


<?php

namespace App\Console\Commands;

use App\Models\Invoice;
use App\Models\ReminderSetting;
use App\Notifications\PaymentReminderNotification;
use Carbon\Carbon;
use Illuminate\Console\Command;

class SendPaymentReminders extends Command
{
protected $signature = 'invoices:send-reminders';
protected $description = 'Send payment reminders for overdue invoices';

public function handle()
{
$settings = ReminderSetting::first();

if (!$settings || !$settings->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}");
}
}
}
}
}
6 changes: 5 additions & 1 deletion app/Console/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
84 changes: 84 additions & 0 deletions app/Filament/App/Resources/ReminderSettingResource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@


<?php

namespace App\Filament\App\Resources;

use App\Models\ReminderSetting;
use Filament\Forms;
use Filament\Resources\Resource;
use Filament\Forms\Form;
use Filament\Resources\Pages\EditRecord;
use Filament\Resources\Pages\ListRecords;
use Filament\Tables;
use Filament\Tables\Table;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Columns\ToggleColumn;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;

class ReminderSettingResource extends Resource
{
protected static ?string $model = ReminderSetting::class;
protected static ?string $navigationIcon = 'heroicon-o-bell';
protected static ?string $navigationLabel = 'Reminder Settings';
protected static ?string $navigationGroup = 'Settings';

public static function form(Form $form): Form
{
return $form
->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'),
];
}
}
24 changes: 24 additions & 0 deletions app/Models/ReminderSetting.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@


<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class ReminderSetting extends Model
{
use HasFactory;

protected $fillable = [
'days_before_reminder',
'reminder_frequency_days',
'max_reminders',
'is_active'
];

protected $casts = [
'is_active' => 'boolean',
];
}
40 changes: 40 additions & 0 deletions app/Notifications/PaymentReminderNotification.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@


<?php

namespace App\Notifications;

use App\Models\Invoice;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;

class PaymentReminderNotification extends Notification implements ShouldQueue
{
use Queueable;

protected $invoice;

public function __construct(Invoice $invoice)
{
$this->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!');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@


<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
public function up(): void
{
Schema::create('reminder_settings', function (Blueprint $table) {
$table->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');
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@


<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
public function up(): void
{
Schema::table('invoices', function (Blueprint $table) {
$table->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']);
});
}
};

0 comments on commit 319ec95

Please sign in to comment.