Notifications
Moon ERP includes an in-app notification system built on Laravel's native notification infrastructure. Notifications are stored in the database and delivered to individual users. The API provides endpoints for listing notifications, marking them as read, and checking unread counts.
Purpose
- Deliver real-time system notifications to users (e.g., journal entry approved, budget submitted)
- Support read/unread status tracking per notification
- Provide an unread count for badge display in the UI
- Enable bulk "mark all as read" functionality
Entity Attributes
Notifications use Laravel's built-in notifications table:
| Field | Type | Description |
|---|---|---|
id | uuid | Primary key (UUID) |
type | string | Notification class name |
notifiable_type | string | Always App\Models\User |
notifiable_id | integer | The user ID |
data | json | Notification payload (title, message, metadata) |
read_at | datetime? | When the notification was read (null = unread) |
created_at | datetime | Creation timestamp |
updated_at | datetime | Last update timestamp |
Notification Data Structure
The data JSON field typically contains:
| Key | Type | Description |
|---|---|---|
title | string | Notification title |
title_ar | string | Notification title (Arabic) |
message | string | Notification body text |
message_ar | string | Notification body text (Arabic) |
module | string? | Source module (e.g., accounting, core) |
action | string? | Action type (e.g., journal_approved, budget_submitted) |
entity_type | string? | Related entity class |
entity_id | integer? | Related entity ID |
Relationships
API Endpoints
| Method | Path | Description |
|---|---|---|
GET | /api/core/notifications | List notifications for the authenticated user |
POST | /api/core/notifications/{id}/read | Mark a specific notification as read |
POST | /api/core/notifications/read-all | Mark all notifications as read |
GET | /api/core/notifications/unread-count | Get the count of unread notifications |
Request/Response Examples
GET /api/core/notifications
Retrieve a paginated list of notifications for the authenticated user.
bash
curl -X GET https://moon-erp.test/api/core/notifications \
-H "Accept: application/json" \
-H "Accept-Language: ar" \
-H "Authorization: Bearer {token}"dart
final response = await http.get(
Uri.parse('https://moon-erp.test/api/core/notifications'),
headers: {
'Accept': 'application/json',
'Accept-Language': 'ar',
'Authorization': 'Bearer $token',
},
);Response 200 OK
json
{
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"type": "Modules\\Accounting\\Notifications\\JournalApproved",
"data": {
"title": "Journal Entry Approved",
"title_ar": "تم اعتماد قيد اليومية",
"message": "Journal entry JE-2026-00015 has been approved",
"message_ar": "تم اعتماد قيد اليومية JE-2026-00015",
"module": "accounting",
"action": "journal_approved",
"entity_type": "Modules\\Accounting\\Models\\JournalEntry",
"entity_id": 15
},
"read_at": null,
"created_at": "2026-02-16T10:00:00.000000Z"
},
{
"id": "660f9511-f30c-52e5-b827-557766551111",
"type": "Modules\\Core\\Notifications\\UserCreated",
"data": {
"title": "New User Added",
"title_ar": "تمت إضافة مستخدم جديد",
"message": "User Fatima Hassan has been added to the system",
"message_ar": "تمت إضافة المستخدمة فاطمة حسن إلى النظام",
"module": "core",
"action": "user_created"
},
"read_at": "2026-02-16T09:30:00.000000Z",
"created_at": "2026-02-15T14:00:00.000000Z"
}
],
"links": { "first": "...?page=1", "last": "...?page=1", "prev": null, "next": null },
"meta": { "current_page": 1, "from": 1, "last_page": 1, "per_page": 25, "to": 2, "total": 2 }
}GET /api/core/notifications/unread-count
Get the number of unread notifications for badge display.
bash
curl -X GET https://moon-erp.test/api/core/notifications/unread-count \
-H "Accept: application/json" \
-H "Authorization: Bearer {token}"dart
final response = await http.get(
Uri.parse('https://moon-erp.test/api/core/notifications/unread-count'),
headers: {
'Accept': 'application/json',
'Authorization': 'Bearer $token',
},
);
final count = jsonDecode(response.body)['unread_count'];
// Use count to display badge: e.g., "3"Response 200 OK
json
{
"unread_count": 3
}POST /api/core/notifications/{id}/read
Mark a single notification as read.
bash
curl -X POST https://moon-erp.test/api/core/notifications/550e8400-e29b-41d4-a716-446655440000/read \
-H "Accept: application/json" \
-H "Authorization: Bearer {token}"dart
final response = await http.post(
Uri.parse('https://moon-erp.test/api/core/notifications/550e8400-e29b-41d4-a716-446655440000/read'),
headers: {
'Accept': 'application/json',
'Authorization': 'Bearer $token',
},
);Response 200 OK
json
{
"message": "Marked as read"
}POST /api/core/notifications/read-all
Mark all unread notifications as read for the authenticated user.
bash
curl -X POST https://moon-erp.test/api/core/notifications/read-all \
-H "Accept: application/json" \
-H "Authorization: Bearer {token}"dart
final response = await http.post(
Uri.parse('https://moon-erp.test/api/core/notifications/read-all'),
headers: {
'Accept': 'application/json',
'Authorization': 'Bearer $token',
},
);
final count = jsonDecode(response.body)['count'];
// e.g., "5 notifications marked as read"Response 200 OK
json
{
"message": "All notifications marked as read",
"count": 5
}Notification Flow
Business Rules
- User scoping -- notifications are always scoped to the authenticated user. A user can only see and manage their own notifications.
- UUID identifiers -- notification IDs are UUIDs, not integers. Always use string type when referencing notification IDs.
- Read status -- a notification is considered unread when
read_atisnull. Marking it as read setsread_atto the current timestamp. - Idempotent read -- marking an already-read notification as read again is safe and does not produce an error.
- Bulk read -- the
read-allendpoint marks all unread notifications as read in a single operation and returns the count of notifications that were marked. - Bilingual content -- notification payloads should include both
title/messageandtitle_ar/message_ar. The client can choose which to display based on the user's locale preference. - No delete endpoint -- notifications cannot be deleted through the API. They persist in the database for audit trail purposes.
- Polling recommended -- the client should periodically poll
GET /api/core/notifications/unread-countto update the notification badge. Consider polling every 30-60 seconds.