# Step 11 — Reminders and Notifications — Implementation Report

**Status:** COMPLETED  
**Verified:** 12 August 2026

## What was implemented

- Automated loan-repayment reminders based on each active loan installment's due date and current unpaid balance.
- Default reminder sequence: 3 days before due date, 1 day before due date, on the due date, 1 day overdue, then continued overdue reminders using a configurable cadence.
- Continued overdue frequencies: daily, every 2 days, every 3 days, or weekly.
- Business-wide configuration for enabling/disabling reminders, pre-due days, due-date notices, overdue notices, continued overdue frequency, and enabled delivery channels.
- Delivery channels: SMS, WhatsApp, email, and in-system notification.
- SMS and WhatsApp provider abstractions use configurable real HTTP endpoints and bearer tokens.
- Email delivery uses Laravel's configured mail transport and refuses to report `Delivered` when only non-delivery transports such as `log` or `array` are configured.
- In-system reminders reuse Laravel's existing database notification infrastructure and are linked to the permanent reminder delivery log.
- Provider responses and provider/network errors are stored in the notification log.
- Non-successful provider responses and exceptions are recorded as `Failed`; failed notifications are never shown as delivered.
- Missing provider configuration is recorded truthfully as a failed delivery instead of simulating success.
- Failed notifications can be retried explicitly by authorized branch users. Retry increments the attempt counter, preserves the original idempotency reference, and records the new provider outcome.
- Scheduled dispatcher claims an occurrence before contacting the provider. Scheduler reruns therefore do not automatically repeat a notification that has already been attempted.
- Deterministic idempotency keys prevent duplicate generation for the same installment, reminder type, scheduled date, and channel.
- Database uniqueness and row locking provide duplicate/concurrency protection in addition to the deterministic key.
- Reminder message amounts use the installment's current `remaining` balance through `MoneyMath`, so partial payments are reflected correctly and floating-point calculations are not used.
- Fully paid installments and loans that are no longer active do not receive repayment reminders.
- Editable notification templates are provided for pre-due, due-date, and overdue events on every supported channel.
- Template edits affect future generated reminders only; generated notification-log messages remain permanent snapshots of what was actually attempted/sent.
- Template tokens include customer, loan, installment, current amount due, due date, days overdue, business, and branch data.
- Default reminder templates are seeded idempotently and do not overwrite existing customized templates.
- Branch-scoped notification history includes customer, loan, installment, recipient, message, channel, scheduled date, status, attempts, provider response, error, and delivered timestamp.
- Notification history can be searched and filtered by status, channel, reminder type, and date range.
- Permissions separate viewing logs, retrying failed branch notifications, and managing business-wide reminder policy/templates.
- Business-wide reminder policy/template management remains global; branch-scoped roles cannot change it.
- Audit events record reminder-policy updates, template edits, and explicit delivery retries.
- Reminder dispatch is registered in Laravel Scheduler at the configured daily time and application timezone with overlapping runs blocked.
- Step 12 arrears/penalty calculation was intentionally not implemented in this phase.

## Database changes

Business settings added:

- `businesses.reminders_enabled`
- `businesses.reminder_pre_due_days`
- `businesses.reminder_due_date_enabled`
- `businesses.reminder_overdue_enabled`
- `businesses.reminder_overdue_frequency`
- `businesses.reminder_channels`

Created `notification_templates` containing:

- business
- reminder event type
- delivery channel
- optional subject
- message body
- active status

Important template protection:

- Unique `(business_id, event_type, channel)` pair.

Created `notification_logs` containing:

- business and branch
- customer
- loan
- loan installment
- template reference
- reminder type
- days relative to due date
- channel
- recipient
- subject and message snapshot
- scheduled date
- delivery status
- provider response
- provider error
- attempt count
- last attempt timestamp
- delivered timestamp
- deterministic idempotency key

Important log protections:

- Unique idempotency key prevents duplicate reminder occurrences.
- Branch/status/date, branch/date, loan/date, and customer/date indexes support operational review.
- Financial/source relationships use protected foreign keys.
- No reminder delivery-log delete route is exposed.

## Main files created or modified

- `database/migrations/2026_08_12_120000_create_reminders_and_notifications.php`
- `app/Models/NotificationTemplate.php`
- `app/Models/ReminderNotificationLog.php`
- `app/Models/Business.php`
- `app/Services/LoanReminderService.php`
- `app/Services/Notifications/NotificationDeliveryResult.php`
- `app/Services/Notifications/ReminderChannelProvider.php`
- `app/Services/Notifications/InSystemReminderProvider.php`
- `app/Services/Notifications/HttpReminderProvider.php`
- `app/Services/Notifications/EmailReminderProvider.php`
- `app/Services/Notifications/ReminderProviderRegistry.php`
- `app/Services/Notifications/ReminderMessageRenderer.php`
- `app/Notifications/LoanReminderNotification.php`
- `app/Console/Commands/DispatchLoanReminders.php`
- `app/Http/Controllers/NotificationSettingsController.php`
- `app/Http/Controllers/ReminderNotificationController.php`
- `database/seeders/NotificationTemplateSeeder.php`
- `database/seeders/DatabaseSeeder.php`
- `database/seeders/RolePermissionSeeder.php`
- `config/reminders.php`
- `.env.example`
- `routes/console.php`
- `routes/notifications.php`
- `routes/web.php`
- `resources/views/notifications/index.blade.php`
- `resources/views/notifications/settings.blade.php`
- `resources/views/layouts/partials/sidebar.blade.php`
- `tests/Feature/ReminderNotificationWorkflowTest.php`

## Routes/endpoints added

Authenticated reminder operations:

- `GET /notifications` — branch-scoped delivery log
- `POST /notifications/{notification}/retry` — retry a failed notification within an authorized branch
- `GET /notification-settings` — business-wide reminder policy and templates
- `PUT /notification-settings`
- `PUT /notification-templates/{template}`

Console/scheduled command:

- `php artisan notifications:dispatch-reminders`
- Optional test/administrative date override: `--date=YYYY-MM-DD`
- Registered with Laravel Scheduler to run daily at `REMINDER_DISPATCH_TIME` in `APP_TIMEZONE` with overlapping executions prevented.

## Provider configuration added

Environment configuration supports:

- `REMINDER_DISPATCH_TIME`
- `REMINDER_SMS_ENDPOINT`
- `REMINDER_SMS_TOKEN`
- `REMINDER_SMS_TIMEOUT`
- `REMINDER_WHATSAPP_ENDPOINT`
- `REMINDER_WHATSAPP_TOKEN`
- `REMINDER_WHATSAPP_TIMEOUT`

Email uses the existing Laravel mail configuration. A real mail transport must be configured before email reminder attempts can be recorded as delivered.

## Permissions added

- `notifications.view` — view branch-scoped reminder logs
- `notifications.retry` — retry failed reminders within assigned branches
- `notifications.manage` — manage business-wide reminder policy and templates

Branch Administrator and Branch Manager receive branch-scoped view/retry permissions. Other relevant operational roles receive view access. `notifications.manage` remains global and is available to Super Administrator through the existing full-permission bypass rather than being granted to branch-scoped roles.

## Automated coverage

Step 11 tests verify:

- Default 3-day, 1-day, due-date, first-overdue, and continued-overdue sequence.
- Each scheduled reminder occurrence is delivered/generated only once when the scheduler is rerun.
- Configurable continued-overdue frequencies.
- Current installment remaining amount is used after a partial payment.
- Missing SMS provider configuration creates a failed log instead of fake delivery.
- Failed scheduled deliveries are not automatically retried on scheduler reruns.
- Explicit authorized retry can deliver successfully and preserves the idempotency reference.
- Provider request payload includes the actual customer recipient and idempotency reference.
- Business-wide settings and templates require global management permission.
- Settings/template changes are audited.
- Notification-log visibility and retry actions are branch isolated.
- Paid installments and non-active loans are excluded.
- Laravel Scheduler registers the daily reminder command at the configured time/timezone.
- Reminder list/settings UI renders successfully using existing compact application patterns.

## Test result at implementation verification

- **92 tests passed**
- **733 assertions passed**
- Fresh migrations and seeders passed.
- All Step 1–10 tests remained green.

## Remaining for Step 11

None for the application-side reminder workflow. Real SMS, WhatsApp, and email delivery requires the deployment to supply its chosen provider endpoint/token or real Laravel mail transport credentials. The application intentionally records missing/unavailable providers as failed rather than simulating delivery.

Arrears ageing, penalty calculation, penalty overrides/waivers, and collection escalation remain Step 12.
