From 234a5b2b9f6f0d2a0e10d6d4cf05a517a51fc87c Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Sun, 12 Apr 2026 20:11:54 -0400 Subject: [PATCH] feat(notifications): workflow hooks + views + menu Co-Authored-By: Claude Opus 4.6 (1M context) --- .../models/account_move.py | 54 ++++++++- .../models/fp_receiving.py | 48 +++++++- .../models/sale_order.py | 47 +++++++- .../views/fp_notification_log_views.xml | 109 ++++++++++++++++++ .../views/fp_notification_template_views.xml | 95 +++++++++++++++ .../views/fp_notifications_menu.xml | 15 +++ 6 files changed, 365 insertions(+), 3 deletions(-) diff --git a/fusion-plating/fusion_plating_notifications/models/account_move.py b/fusion-plating/fusion_plating_notifications/models/account_move.py index 3a4b0a41..509b0ecd 100644 --- a/fusion-plating/fusion_plating_notifications/models/account_move.py +++ b/fusion-plating/fusion_plating_notifications/models/account_move.py @@ -3,4 +3,56 @@ # License OPL-1 (Odoo Proprietary License v1.0) # Part of the Fusion Plating product family. -# Placeholder — implemented in Task 3. +import logging + +from odoo import models + +_logger = logging.getLogger(__name__) + + +class AccountMove(models.Model): + _inherit = 'account.move' + + def action_post(self): + res = super().action_post() + for move in self: + if move.move_type == 'out_invoice' and move.partner_id: + # Find linked SO + so = False + if move.invoice_origin: + so = self.env['sale.order'].search( + [('name', '=', move.invoice_origin)], limit=1, + ) + self._send_fp_notification( + 'invoice_posted', move, move.partner_id, sale_order=so, + ) + return res + + def _send_fp_notification(self, trigger_event, record, partner, sale_order=None): + """Send a notification email and log it.""" + template = self.env['fp.notification.template'].search( + [('trigger_event', '=', trigger_event), ('active', '=', True)], limit=1, + ) + if not template or not template.mail_template_id: + return + try: + template.mail_template_id.send_mail(record.id, force_send=False) + self.env['fp.notification.log'].create({ + 'template_id': template.id, + 'trigger_event': trigger_event, + 'sale_order_id': sale_order.id if sale_order else False, + 'partner_id': partner.id if partner else False, + 'recipient_email': partner.email if partner else '', + 'status': 'sent', + }) + except Exception as e: + _logger.warning('FP notification failed (%s): %s', trigger_event, e) + self.env['fp.notification.log'].create({ + 'template_id': template.id, + 'trigger_event': trigger_event, + 'sale_order_id': sale_order.id if sale_order else False, + 'partner_id': partner.id if partner else False, + 'recipient_email': partner.email if partner else '', + 'status': 'failed', + 'error_message': str(e), + }) diff --git a/fusion-plating/fusion_plating_notifications/models/fp_receiving.py b/fusion-plating/fusion_plating_notifications/models/fp_receiving.py index 3a4b0a41..d692f5d1 100644 --- a/fusion-plating/fusion_plating_notifications/models/fp_receiving.py +++ b/fusion-plating/fusion_plating_notifications/models/fp_receiving.py @@ -3,4 +3,50 @@ # License OPL-1 (Odoo Proprietary License v1.0) # Part of the Fusion Plating product family. -# Placeholder — implemented in Task 3. +import logging + +from odoo import models + +_logger = logging.getLogger(__name__) + + +class FpReceiving(models.Model): + _inherit = 'fp.receiving' + + def action_accept(self): + res = super().action_accept() + for rec in self: + self._send_fp_notification( + 'parts_received', rec, rec.partner_id, + sale_order=rec.sale_order_id, + ) + return res + + def _send_fp_notification(self, trigger_event, record, partner, sale_order=None): + """Send a notification email and log it.""" + template = self.env['fp.notification.template'].search( + [('trigger_event', '=', trigger_event), ('active', '=', True)], limit=1, + ) + if not template or not template.mail_template_id: + return + try: + template.mail_template_id.send_mail(record.id, force_send=False) + self.env['fp.notification.log'].create({ + 'template_id': template.id, + 'trigger_event': trigger_event, + 'sale_order_id': sale_order.id if sale_order else False, + 'partner_id': partner.id if partner else False, + 'recipient_email': partner.email if partner else '', + 'status': 'sent', + }) + except Exception as e: + _logger.warning('FP notification failed (%s): %s', trigger_event, e) + self.env['fp.notification.log'].create({ + 'template_id': template.id, + 'trigger_event': trigger_event, + 'sale_order_id': sale_order.id if sale_order else False, + 'partner_id': partner.id if partner else False, + 'recipient_email': partner.email if partner else '', + 'status': 'failed', + 'error_message': str(e), + }) diff --git a/fusion-plating/fusion_plating_notifications/models/sale_order.py b/fusion-plating/fusion_plating_notifications/models/sale_order.py index 3a4b0a41..72002c92 100644 --- a/fusion-plating/fusion_plating_notifications/models/sale_order.py +++ b/fusion-plating/fusion_plating_notifications/models/sale_order.py @@ -3,4 +3,49 @@ # License OPL-1 (Odoo Proprietary License v1.0) # Part of the Fusion Plating product family. -# Placeholder — implemented in Task 3. +import logging + +from odoo import models + +_logger = logging.getLogger(__name__) + + +class SaleOrder(models.Model): + _inherit = 'sale.order' + + def action_confirm(self): + res = super().action_confirm() + for order in self: + self._send_fp_notification( + 'so_confirmed', order, order.partner_id, sale_order=order, + ) + return res + + def _send_fp_notification(self, trigger_event, record, partner, sale_order=None): + """Send a notification email and log it.""" + template = self.env['fp.notification.template'].search( + [('trigger_event', '=', trigger_event), ('active', '=', True)], limit=1, + ) + if not template or not template.mail_template_id: + return + try: + template.mail_template_id.send_mail(record.id, force_send=False) + self.env['fp.notification.log'].create({ + 'template_id': template.id, + 'trigger_event': trigger_event, + 'sale_order_id': sale_order.id if sale_order else False, + 'partner_id': partner.id if partner else False, + 'recipient_email': partner.email if partner else '', + 'status': 'sent', + }) + except Exception as e: + _logger.warning('FP notification failed (%s): %s', trigger_event, e) + self.env['fp.notification.log'].create({ + 'template_id': template.id, + 'trigger_event': trigger_event, + 'sale_order_id': sale_order.id if sale_order else False, + 'partner_id': partner.id if partner else False, + 'recipient_email': partner.email if partner else '', + 'status': 'failed', + 'error_message': str(e), + }) diff --git a/fusion-plating/fusion_plating_notifications/views/fp_notification_log_views.xml b/fusion-plating/fusion_plating_notifications/views/fp_notification_log_views.xml index 6fa84137..b710694c 100644 --- a/fusion-plating/fusion_plating_notifications/views/fp_notification_log_views.xml +++ b/fusion-plating/fusion_plating_notifications/views/fp_notification_log_views.xml @@ -1,3 +1,112 @@ + + + + + + fp.notification.log.list + fp.notification.log + + + + + + + + + + + + + + + + + fp.notification.log.form + fp.notification.log + +
+ + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + + + + fp.notification.log.search + fp.notification.log + + + + + + + + + + + + + + + + + + + + + + + + + Notification Log + fp.notification.log + list,form + + +

+ No notifications sent yet +

+

+ This log records every notification email sent by the system, + including failures for troubleshooting. +

+
+
+
diff --git a/fusion-plating/fusion_plating_notifications/views/fp_notification_template_views.xml b/fusion-plating/fusion_plating_notifications/views/fp_notification_template_views.xml index 6fa84137..a77aecfd 100644 --- a/fusion-plating/fusion_plating_notifications/views/fp_notification_template_views.xml +++ b/fusion-plating/fusion_plating_notifications/views/fp_notification_template_views.xml @@ -1,3 +1,98 @@ + + + + + + fp.notification.template.list + fp.notification.template + + + + + + + + + + + + + + + fp.notification.template.form + fp.notification.template + +
+ +
+

+ +

+
+ + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + + + + fp.notification.template.search + fp.notification.template + + + + + + + + + + + + + + + Notification Templates + fp.notification.template + list,form + + {'active_test': False} + +

+ Create a notification template +

+

+ Map trigger events to email templates to automatically notify + customers at key workflow milestones. +

+
+
+
diff --git a/fusion-plating/fusion_plating_notifications/views/fp_notifications_menu.xml b/fusion-plating/fusion_plating_notifications/views/fp_notifications_menu.xml index 6fa84137..025a0471 100644 --- a/fusion-plating/fusion_plating_notifications/views/fp_notifications_menu.xml +++ b/fusion-plating/fusion_plating_notifications/views/fp_notifications_menu.xml @@ -1,3 +1,18 @@ + + + + +