diff --git a/controllers/main.py b/controllers/main.py
index 4073256..1557f00 100644
--- a/controllers/main.py
+++ b/controllers/main.py
@@ -26,29 +26,41 @@ class AppNotificationController(http.Controller):
def fetch_notifications(self, **kw):
"""
Endpoint for the Flutter app Background Task and In-App notification center.
+ Returns image_128 (base64 thumbnail) for list display.
+ The Flutter detail screen loads the full image via /web/image/ with session cookie.
"""
+ import base64 as b64mod
user = request.env.user
partner = user.partner_id
-
+
last_id = kw.get('last_id', 0)
-
+
# Give them global notifications OR notifications tagged explicitly to them
domain = [
('id', '>', last_id),
- '|',
+ '|',
('partner_ids', 'in', [partner.id]),
('is_global', '=', True)
]
-
+
notifications = request.env['mapan.app.notification'].sudo().search_read(
domain,
- ['id', 'title', 'body', 'create_date'],
+ ['id', 'title', 'body', 'create_date', 'image_128'],
order='create_date desc',
limit=50
)
-
+
+ # Normalize image_128 bytes → base64 string for JSON transport
+ for notif in notifications:
+ img = notif.get('image_128')
+ if img and isinstance(img, bytes):
+ notif['image_128'] = b64mod.b64encode(img).decode('utf-8')
+ elif not img:
+ notif['image_128'] = None
+ notif['has_image'] = bool(notif['image_128'])
+
return {
- 'status': 'success',
+ 'status': 'success',
'data': notifications
}
diff --git a/models/app_notification.py b/models/app_notification.py
index 7a903dd..978b625 100644
--- a/models/app_notification.py
+++ b/models/app_notification.py
@@ -1,4 +1,4 @@
-from odoo import models, fields
+from odoo import models, fields, api
class AppNotification(models.Model):
_name = 'mapan.app.notification'
@@ -7,20 +7,37 @@ class AppNotification(models.Model):
title = fields.Char(string='Notification Title', required=True)
body = fields.Text(string='Notification Body', required=True)
-
+
+ # Optional promotional image — Odoo auto-generates image_128 thumbnail
+ image = fields.Image(
+ string='Notification Image',
+ max_width=1920,
+ max_height=1920,
+ help='Optional image shown in the notification detail screen.'
+ )
+ image_128 = fields.Image(
+ string='Thumbnail',
+ related='image',
+ max_width=128,
+ max_height=128,
+ store=True,
+ readonly=True,
+ )
+
# If no partner_ids are selected, it is broadcast to everyone logically
partner_ids = fields.Many2many(
- 'res.partner',
+ 'res.partner',
string='Selected Customers',
help='Leave empty to broadcast to all app users.'
)
-
+
is_global = fields.Boolean(
string='Broadcast to All?',
compute='_compute_is_global',
store=True
)
+ @api.depends('partner_ids')
def _compute_is_global(self):
for rec in self:
rec.is_global = not bool(rec.partner_ids)
diff --git a/wizard/push_wizard.py b/wizard/push_wizard.py
index 3f98107..c9199f8 100644
--- a/wizard/push_wizard.py
+++ b/wizard/push_wizard.py
@@ -7,6 +7,12 @@ class PushNotificationWizard(models.TransientModel):
title = fields.Char(string='Notification Title', required=True)
body = fields.Text(string='Notification Body', required=True)
+ image = fields.Image(
+ string='Notification Image (optional)',
+ max_width=1920,
+ max_height=1920,
+ help='Upload a promotional image to display in the notification detail.'
+ )
recipient_type = fields.Selection([
('all', 'All Activated Members'),
('specific', 'Specific Members')
@@ -26,6 +32,7 @@ class PushNotificationWizard(models.TransientModel):
self.env['mapan.app.notification'].create({
'title': self.title,
'body': self.body,
+ 'image': self.image,
})
target_msg = 'all activated app members'
else:
@@ -33,6 +40,7 @@ class PushNotificationWizard(models.TransientModel):
self.env['mapan.app.notification'].create({
'title': self.title,
'body': self.body,
+ 'image': self.image,
'partner_ids': [(6, 0, self.partner_ids.ids)],
})
target_msg = f'{len(self.partner_ids)} selected member(s)'
diff --git a/wizard/push_wizard_views.xml b/wizard/push_wizard_views.xml
index 8e09ec9..c53e060 100644
--- a/wizard/push_wizard_views.xml
+++ b/wizard/push_wizard_views.xml
@@ -8,6 +8,8 @@
+