# Agenda — subscriptions.mycloud.mu

## What This App Does
A **Subscription Renewal Reminder System** for My Cloud (mycloud.mu) — tracks B2B software subscriptions, calculates annual renewal dates, and sends tiered email + SMS reminders at 60, 30, and 3 days before each subscription's anniversary.

---

## Stack
| Layer | Tech |
|---|---|
| Backend | Laravel 11 |
| Admin Panel | Backpack for Laravel (Pro) + Tabler theme |
| Email Service | ZeptoMail API (Zoho) |
| SMS Service | Custom SMS API (env `SMS_API_URL`) |
| Queue | Database queue (`php artisan queue:work`) |
| Database | SQLite (or MySQL via .env) |
| Audit | Spatie Activity Log (via `backpack/activity-log`) |
| Charts | ConsoleTVs Charts 6 |

---

## Data Models

### User (`users`)
Admin users only. All have full Backpack access (no role separation yet).
- Seed admin: `admin@mycloud.mu`

### Product (`products`)
Simple lookup: `id`, `name`. E.g. "Microsoft 365", "Google Workspace".

### Subscription (`subscriptions`)
Core entity.
| Field | Notes |
|---|---|
| `customer_name` | Company/person name |
| `email1/2/3` | Up to 3 reminder recipients |
| `phone1/2/3` | Up to 3 SMS recipients |
| `product_id` | FK → products |
| `start_date` | Contract start (anniversary base) |
| `contract_value` | Annual value in MUR (integer) |
| `active` | Boolean — inactive subscriptions skipped by cron |
| `updated_by` | FK → users (audit trail) |

**Key computed attribute**: `next_anniversary` — returns the next occurrence of the start_date month/day, rolling to next year if already past.

### email_log / sms_log
Audit tables for every sent notification. Both store `subscription_id`, recipient, content, and the raw API response (JSON).

---

## Reminder Flow

```
Daily external cron → GET /cron-jobs
    └── Cron::cronJobs()
        ├── dispatchReminders(60) → finds subscriptions with anniversary in exactly 60 days
        ├── dispatchReminders(30) → 30 days
        └── dispatchReminders(3)  → 3 days
            └── per subscription: dispatch SendReminderJob (email) + SendReminderJob (sms)

Queue worker processes jobs:
    SendReminderJob::handle()
        ├── sendEmailReminder() → ZeptoMail API → log to email_log
        └── sendSMSReminder()  → SMS API → log to sms_log
```

**Reminder intervals**: 60 days (friendly), 30 days (invoice sent), 3 days (final/urgent).
**Email recipients**: email1/2/3 + always `accounting@mycloud.mu`.
**SMS recipients**: phone1/2/3 only.

---

## Key Files
| File | Purpose |
|---|---|
| [app/Http/Controllers/Cron.php](app/Http/Controllers/Cron.php) | Cron entry point — dispatches all reminder jobs |
| [app/Jobs/SendReminderJob.php](app/Jobs/SendReminderJob.php) | Sends email or SMS for one subscription |
| [app/Models/Subscription.php](app/Models/Subscription.php) | Core model; `next_anniversary` attr; `subscriptionsWithAnniversaryInNextDays()` |
| [app/Http/Controllers/Admin/SubscriptionCrudController.php](app/Http/Controllers/Admin/SubscriptionCrudController.php) | Backpack CRUD for subscriptions |
| [config/reminders.php](config/reminders.php) | Email template names + SMS message strings per interval |
| [resources/views/emails/](resources/views/emails/) | Blade templates for 60/30/3-day emails |
| [routes/web.php](routes/web.php) | `/cron-jobs` route + root redirect |
| [routes/backpack/custom.php](routes/backpack/custom.php) | Admin CRUD + chart routes |

---

## Dashboard Charts
- **Products Split** (`/admin/charts/products-split`): Bar chart — total contract value by product.
- **Subscriptions Due by Month** (`/admin/charts/subscription-due-by-month`): Bar chart — total renewal value per calendar month (active only).

---

## Environment Variables (Critical)
```
APP_TIMEZONE=Indian/Mauritius   # UTC+4

# ZeptoMail (email)
ZEPTOMAIL_API_KEY=

# SMS
SMS_API_URL=
SMS_API_KEY=

# Sender identity
EMAIL_FROM=notifications@mycloud.mu

# Queue must be running
QUEUE_CONNECTION=database
```

---

## Known Gaps / Potential Work Items
1. **No reminder-sent tracking** — no flag/log on the subscription itself to indicate reminders have been dispatched; debugging requires checking `email_log`/`sms_log` manually.
2. **HTTP-triggered cron** — `/cron-jobs` is public (no auth/secret). Consider adding a signed URL or `APP_KEY`-based token check.
3. **Duplicate send risk** — if the cron fires more than once a day, duplicates are sent. No idempotency check.
4. **No soft deletes** — subscriptions can't be deleted via UI, but no `SoftDeletes` trait either; hard-deleted records would leave orphaned logs.
5. **Single user role** — all admin users have identical permissions; no granular access control.
6. **Queue worker not managed** — no Supervisor config bundled; queue:work must be started manually or via hosting panel.
7. **Email template styling** — basic Blade templates, no consistent My Cloud branding/CSS.
8. **`start_date` stored as `datetime`** — only the month/day matters for anniversary; time component unused.

---

## Conventions (from CLAUDE.md)
- Use DB transactions for any multi-table write.
- Comment every function with 1-2 lines + date.
- Do not modify committed migrations.
- Avoid raw SQL unless strictly necessary.
- Do not make code changes without explicit request — ask first if unsure.
