DEV: Probation Tracking

This commit is contained in:
Bhagya-K 2026-06-22 15:02:55 +05:30
parent 19669a7a3a
commit 7f8a627a8c
9 changed files with 323 additions and 0 deletions

View File

@ -0,0 +1,2 @@
from . import tools
from . import models

View File

@ -0,0 +1,21 @@
{
"name": "Firebase Integration",
"version": "18.0.1.0.0",
"category": "Tools",
"author": "IdeaCode Academy",
"license": "LGPL-3",
"summary": "Integrate Firebase services and push notifications with Odoo",
"depends": ["base", "bus","mail"],
"external_dependencies": {
"python": ["firebase-admin"],
},
"data": [
"views/res_users.xml",
],
"images": ["static/description/banner.png"],
"installable": True,
"application": False,
"auto_install": False,
"price": 0.0,
"currency": "USD",
}

View File

@ -0,0 +1,13 @@
{
"type": "service_account",
"project_id": "hrmsopzento",
"private_key_id": "60336fd85549183b6c890121aaa3f08256d3f0a4",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDZn3agMptdcJ7n\noZIS1E0x/3PBB0uPVljoQRrqDwwEPtwKC+Sh9DY/eiXbvXyQGfzH3LkToVgvNPCY\nsFRfPnGtp9W45bwYOi0qXc8MWzYiBLRfO663hOjh90AfTEgE02wD3DOWo9eFlzMn\nJMPrOZUD0fNpzQFXQ7o8ufdrCS40d4hDQNiMGSx2hPPOnmJioz2GOr1ABhxNFJQY\nv53L1PEP1kCEnoQcCKfpuMPNY2ApzlIEndLoBhr1W5RsHp9web22CukqtJYzVCbw\naWJymZQFSkos5rmCSCvkaTvtEYBkl//4t++WjbNSxL8C48EH1O97Hg4djYULnL1D\n/GmSIYibAgMBAAECggEAVHkYk+Bw/Fk95U2LJPHxsQmmhfPt+Yqb4jN7XgVPNcqs\noN2y9saT1Bn23g/0bP8ZZv8ffCYx08kp5yry5TGY8L5oMGhElebnJz3Yo8Q4BAZt\neVXyYNwfha7y3fM/NVhX4ju0brHUc8+YFIap4gGs/Rme8Z+Y+KWagf3xs0OSAtz3\n0d6rOsMl7UfJojChrx70CrwB0y+O+3vbD+IzInWq0RBjcu78VqCiUP+X6BuKmSgN\n2qp61FrW6eNgY1M6thL2UZeDke5hEfLcjWcdSskTj7/yONErzgFiT754IiWXMGPd\n0gGKBAJKkmmBy1stGR/5JoaqV8fVt8GFypm1LZcJEQKBgQD1EEJgyV6IyPWGzu8m\nYhW80zSQ0fQxPX9Y62UP52TecUY5qMvMOmBdec8LE18IKIRgBesCvtdtv2am0rLs\nXg/1Yrfm7AsR/eEYRY3E/8Oyk+XVteUxGR6ZgAIUSLXAsnsfx3/5CkqVTszASime\nvK2httSvmHwWaWvALnAFv3oncwKBgQDjVbUa/jBxf4N2Hcf4oYTJSfkTWa2Llwvm\n63yjcPaqm6HS1psgPd4ZiITEg4UOYpL8Cngb5hqRRIusZGatdBxkUi3AunDuOORL\nYGcn2Zka3aXtTqXzXyhXR3aQH3m2wn7KGDUs4lcoUC/x30UQKqNpWvIpfelLdNgA\n5ZyniSZAOQKBgQDu0Kt/Gn3Pmtb6SorvwsIgQ0qEnrXzjlSd2Leh6gN4arbe1cnU\n+kaSkXPc/UGs958Y3GuLP2M9BjsI82d9xKSUo2FH3ltjax+CwbVId17EljByNVJm\nqG4TdJWSItFMOiKWc5oYnZjVK/eIpD0u/fvPDhbyEA1M4espW5e7Yj+uVQKBgC2g\n7ELItixxrY8tlw9+S8qjAE0z+LNF0+u7ZD7h04CW0DojPOuRv1xcnFldFH24p0vT\nRhxDaR2zJl2poTo7Td+M5wYB5dzKqne+l7XV5PcRedZRrNlWRiCOhWuUBbf6/bvO\ndA3YOCotPhJL/+6owDfLO0O8s/CjOR+k9nZh/r1xAoGAbJJoQhBrF8sCHuTi9Hev\nRrwKnL8Ck5HPB19SkifWA9lg604w2LfRJ1ijOKSm9G1ADLT5a6XfsU7tM/6oG5Np\n3tBxhgEpX03+NS3cwRgYGdzVNS+X5D/9uduCiN0Y4cDUjyin1OT+acOOhd0YtdGe\n9Rp7Ae9GrOKPQ5GvRazbmOI=\n-----END PRIVATE KEY-----\n",
"client_email": "firebase-adminsdk-fbsvc@hrmsopzento.iam.gserviceaccount.com",
"client_id": "111625966108394286408",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-fbsvc%40hrmsopzento.iam.gserviceaccount.com",
"universe_domain": "googleapis.com"
}

View File

@ -0,0 +1,155 @@
from odoo import models
import logging
import re
_logger = logging.getLogger(__name__)
class DiscussChannelFirebaseNotify(models.Model):
_inherit = "discuss.channel"
def _notify_thread(self, message, msg_vals=False, **kwargs):
"""
Extend Odoo Discuss notifications with Firebase push notifications.
"""
res = super()._notify_thread(
message,
msg_vals=msg_vals,
**kwargs
)
try:
recipients = self._notify_get_recipients(
message,
msg_vals or {},
**kwargs
)
_logger.info(
"Firebase notification processing. Channel=%s Message=%s Recipients=%s",
self.display_name,
message.id,
len(recipients),
)
notifications = []
for recipient in recipients:
user = False
# Direct user recipient
if recipient.get("uid"):
user = (
self.env["res.users"]
.sudo()
.browse(recipient["uid"])
)
# Channel member recipient (uid=False)
elif recipient.get("id"):
partner = (
self.env["res.partner"]
.sudo()
.browse(recipient["id"])
)
user = partner.user_ids[:1]
if not user:
continue
token = user.token
if not token:
_logger.info(
"Skipping user %s: no token",
user.name,
)
continue
# Record name
record_name = (
msg_vals.get("record_name")
if msg_vals and isinstance(msg_vals, dict)
else message.record_name
)
# Author
author = message.author_id
if self.channel_type == "chat":
title = author.name or "New Message"
elif self.channel_type == "channel":
title = (
f"#{record_name} - {author.name}"
if record_name
else author.name
)
elif self.channel_type == "group":
title = (
f"{record_name} - {author.name}"
if record_name
else author.name
)
else:
title = record_name or author.name or "New Message"
# Remove html tags
body_html = (
msg_vals.get("body")
if msg_vals and msg_vals.get("body")
else message.body
)
body = re.sub(
r"<[^>]+>",
"",
body_html or "",
).strip()
payload = {
"action": "mail.action_discuss",
"channel_id": str(self.id),
"message_id": str(message.id),
}
if self.channel_type:
payload["channel_type"] = self.channel_type
notifications.append({
"token": token,
"title": title,
"body": body,
"data": payload,
})
_logger.info(
"Prepared Firebase notification for user=%s",
user.name,
)
if notifications:
_logger.info(
"Sending %s Firebase notifications",
len(notifications),
)
self.env["firebase.service"].send_notification(
notifications
)
else:
_logger.info(
"No Firebase notifications to send"
)
except Exception as exc:
_logger.exception(
"Firebase notification error: %s",
exc
)
return res

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View File

@ -0,0 +1,21 @@
<section class="oe_container">
<div class="oe_row oe_padded">
<h2 class="oe_slogan" style="color:#7C7BAD;">Firebase Integration</h2>
<h3>Configuration</h3>
<p>Add the following key to your <code>odoo.conf</code> file:</p>
<pre style="background-color: #f8f9fa; border: 1px solid #dee2e6; padding: 10px; border-radius: 4px;"><code>[options]
firebase_private_key_path = /home/documents/cre.json</code></pre>
<h3>Usage</h3>
<p>Import and call the function anywhere within your backend Odoo modules:</p>
<pre style="background-color: #f8f9fa; border: 1px solid #dee2e6; padding: 10px; border-radius: 4px;"><code>from odoo.addons.firebase_integration.tools.firebase import send_firebase_notifications
msg = [
{'token': 'DEVICE_TOKEN_1', 'body': 'Notification Body', 'title': 'Notification Title'},
{'token': 'DEVICE_TOKEN_2', 'body': 'Another Body', 'title': 'Another Title'}
]
success_sent_message_count = send_firebase_notifications(msg)</code></pre>
</div>
</section>

View File

@ -0,0 +1,2 @@
from . import firebase
from . import firebase_service

View File

@ -0,0 +1,109 @@
import logging
import firebase_admin
from firebase_admin import credentials, messaging
from odoo.tools import config
# Fetch the credential file path safely from odoo.conf
firebase_key_path = config.get("firebase_private_key_path")
_logger = logging.getLogger(__name__)
# Initialize the app safely to prevent "app already exists" runtime crashes
try:
firebase_app = firebase_admin.get_app()
except ValueError:
if firebase_key_path:
firebase_cred = credentials.Certificate(firebase_key_path)
firebase_app = firebase_admin.initialize_app(firebase_cred)
else:
# Fallback to default application credentials if config key is missing
firebase_app = firebase_admin.initialize_app()
def send_firebase_notifications(messages: list) -> int:
"""Sends list of notification messages using Firebase Cloud Messaging.
Expecting input layout:
[
{'token': '...', 'body': '...', 'title': '...'},
]
"""
if not messages:
return 0
# Build the payload using native Firebase messaging objects
fcm_messages = []
for msg in messages:
token = msg.get("token")
if not token:
continue
notification = messaging.Notification(
title=msg.get("title", ""),
body=msg.get("body", ""),
)
# include optional data payload
data_payload = msg.get("data")
fcm_messages.append(
messaging.Message(
notification=notification,
token=token,
data=data_payload if isinstance(data_payload, dict) else None,
)
)
if not fcm_messages:
return 0
# Execute the batch transmission using the Firebase Admin SDK
try:
# The SDK / FCM backend limits batch sizes; be conservative and chunk (500 is a safe upper bound).
success_total = 0
chunk_size = 500
for i in range(0, len(fcm_messages), chunk_size):
batch = fcm_messages[i : i + chunk_size]
# Try newer API first (send_all), fall back to send_multicast for older SDK versions
if hasattr(messaging, 'send_all'):
response = messaging.send_all(batch)
_logger.info(
"Firebase Success Count: %s",
response.success_count
)
_logger.info(
"Firebase Failure Count: %s",
response.failure_count
)
for idx, resp in enumerate(response.responses):
if not resp.success:
_logger.error(
"Firebase Error %s: %s",
idx,
resp.exception
)
else:
# Older firebase-admin SDK: use send_multicast
from firebase_admin.messaging import MulticastMessage
multicast = MulticastMessage(tokens=[m.token for m in batch if m.token])
# send_multicast doesn't support per-token custom payloads; use individual sends as fallback
success_count = 0
for msg in batch:
try:
messaging.send(msg)
success_count += 1
except Exception as e:
_logger.debug("Failed to send message to token %s: %s", msg.token, e)
response = type('obj', (object,), {'success_count': success_count})()
sent = getattr(response, "success_count", 0)
success_total += sent
_logger.info("Firebase: sent %s/%s messages in chunk", sent, len(batch))
return success_total
except Exception as exc: # catch firebase exceptions and log them
_logger.exception("Failed to send firebase notifications: %s", exc)
return 0