From 7ecc47983d14000274044e3fddb4a8735716b16d Mon Sep 17 00:00:00 2001 From: Suherdy Yacob Date: Sat, 21 Mar 2026 15:21:52 +0700 Subject: [PATCH] first commit --- __init__.py | 3 +++ __manifest__.py | 20 ++++++++++++++++++++ controllers/__init__.py | 1 + controllers/main.py | 34 ++++++++++++++++++++++++++++++++++ models/__init__.py | 2 ++ models/app_notification.py | 26 ++++++++++++++++++++++++++ models/res_partner.py | 9 +++++++++ security/ir.model.access.csv | 3 +++ views/res_partner_views.xml | 17 +++++++++++++++++ wizard/__init__.py | 1 + wizard/push_wizard.py | 35 +++++++++++++++++++++++++++++++++++ wizard/push_wizard_views.xml | 35 +++++++++++++++++++++++++++++++++++ 12 files changed, 186 insertions(+) create mode 100644 __init__.py create mode 100644 __manifest__.py create mode 100644 controllers/__init__.py create mode 100644 controllers/main.py create mode 100644 models/__init__.py create mode 100644 models/app_notification.py create mode 100644 models/res_partner.py create mode 100644 security/ir.model.access.csv create mode 100644 views/res_partner_views.xml create mode 100644 wizard/__init__.py create mode 100644 wizard/push_wizard.py create mode 100644 wizard/push_wizard_views.xml diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..48d9904 --- /dev/null +++ b/__init__.py @@ -0,0 +1,3 @@ +from . import models +from . import controllers +from . import wizard diff --git a/__manifest__.py b/__manifest__.py new file mode 100644 index 0000000..ac4791b --- /dev/null +++ b/__manifest__.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +{ + 'name': "Mapan Loyalty Push Notifications", + 'summary': "Send push notifications to the Loyalty Flutter App", + 'description': """ + Integrates Odoo with Firebase Cloud Messaging (FCM) to send push notifications directly + to customers' Android devices. Maps FCM tokens to res_partner records. + """, + 'author': "Mapan Group", + 'category': 'Marketing', + 'version': '1.0', + 'depends': ['base', 'loyalty'], + 'data': [ + 'security/ir.model.access.csv', + 'wizard/push_wizard_views.xml', + 'views/res_partner_views.xml', + ], + 'installable': True, + 'application': False, +} diff --git a/controllers/__init__.py b/controllers/__init__.py new file mode 100644 index 0000000..12a7e52 --- /dev/null +++ b/controllers/__init__.py @@ -0,0 +1 @@ +from . import main diff --git a/controllers/main.py b/controllers/main.py new file mode 100644 index 0000000..4c9bd92 --- /dev/null +++ b/controllers/main.py @@ -0,0 +1,34 @@ +from odoo import http +from odoo.http import request + +class AppNotificationController(http.Controller): + + @http.route('/api/loyalty/fetch_notifications', type='json', auth='user', methods=['POST'], csrf=False) + def fetch_notifications(self, **kw): + """ + Endpoint for the Flutter app Background Task and In-App notification center. + """ + 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'], + order='create_date desc', + limit=50 + ) + + return { + 'status': 'success', + 'data': notifications + } diff --git a/models/__init__.py b/models/__init__.py new file mode 100644 index 0000000..b09769f --- /dev/null +++ b/models/__init__.py @@ -0,0 +1,2 @@ +from . import res_partner +from . import app_notification diff --git a/models/app_notification.py b/models/app_notification.py new file mode 100644 index 0000000..7a903dd --- /dev/null +++ b/models/app_notification.py @@ -0,0 +1,26 @@ +from odoo import models, fields + +class AppNotification(models.Model): + _name = 'mapan.app.notification' + _description = 'Mobile App Promotional Notification' + _order = 'create_date desc' + + title = fields.Char(string='Notification Title', required=True) + body = fields.Text(string='Notification Body', required=True) + + # If no partner_ids are selected, it is broadcast to everyone logically + partner_ids = fields.Many2many( + '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 + ) + + def _compute_is_global(self): + for rec in self: + rec.is_global = not bool(rec.partner_ids) diff --git a/models/res_partner.py b/models/res_partner.py new file mode 100644 index 0000000..2e791d8 --- /dev/null +++ b/models/res_partner.py @@ -0,0 +1,9 @@ +from odoo import models, fields + +class ResPartner(models.Model): + _inherit = 'res.partner' + + fcm_token = fields.Char( + string='FCM Device Token', + help='Firebase Cloud Messaging token for sending push notifications via mobile app.' + ) diff --git a/security/ir.model.access.csv b/security/ir.model.access.csv new file mode 100644 index 0000000..a854d0b --- /dev/null +++ b/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_mapan_push_wizard,mapan_push_wizard,model_mapan_push_wizard,base.group_user,1,1,1,1 +access_mapan_app_notification,mapan_app_notification,model_mapan_app_notification,base.group_user,1,1,1,1 diff --git a/views/res_partner_views.xml b/views/res_partner_views.xml new file mode 100644 index 0000000..a25d38e --- /dev/null +++ b/views/res_partner_views.xml @@ -0,0 +1,17 @@ + + + + res.partner.form.fcm + res.partner + + + + + + + + + + + + diff --git a/wizard/__init__.py b/wizard/__init__.py new file mode 100644 index 0000000..43bb8fc --- /dev/null +++ b/wizard/__init__.py @@ -0,0 +1 @@ +from . import push_wizard diff --git a/wizard/push_wizard.py b/wizard/push_wizard.py new file mode 100644 index 0000000..149522b --- /dev/null +++ b/wizard/push_wizard.py @@ -0,0 +1,35 @@ +from odoo import models, fields, api +from odoo.exceptions import UserError + +class PushNotificationWizard(models.TransientModel): + _name = 'mapan.push.wizard' + _description = 'Send Mobile App Notification' + + title = fields.Char(string='Notification Title', required=True) + body = fields.Text(string='Notification Body', required=True) + partner_ids = fields.Many2many( + 'res.partner', + string='Recipients' + ) + + def action_send_push(self): + # Create the notification record in the DB instead of calling Firebase + self.env['mapan.app.notification'].create({ + 'title': self.title, + 'body': self.body, + 'partner_ids': [(6, 0, self.partner_ids.ids)] + }) + + recipient_count = len(self.partner_ids) + target_msg = f"{recipient_count} selected partners" if recipient_count > 0 else "all app users" + + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'title': 'App Notification Dispatched', + 'message': f'Message successfully staged for {target_msg}!', + 'type': 'success', + 'sticky': False, + } + } diff --git a/wizard/push_wizard_views.xml b/wizard/push_wizard_views.xml new file mode 100644 index 0000000..b9a7882 --- /dev/null +++ b/wizard/push_wizard_views.xml @@ -0,0 +1,35 @@ + + + + mapan.push.wizard.form + mapan.push.wizard + +
+ + + + + + + +
+
+
+
+
+ + + Send Push Notification + mapan.push.wizard + form + new + + + +