diff --git a/sale_exception_portal/__init__.py b/sale_exception_portal/__init__.py
new file mode 100644
index 00000000..0650744f
--- /dev/null
+++ b/sale_exception_portal/__init__.py
@@ -0,0 +1 @@
+from . import models
diff --git a/sale_exception_portal/__manifest__.py b/sale_exception_portal/__manifest__.py
new file mode 100644
index 00000000..4846e95f
--- /dev/null
+++ b/sale_exception_portal/__manifest__.py
@@ -0,0 +1,22 @@
+{
+ 'name': 'Sale Exception Portal',
+ 'summary': 'Display sale exceptions on customer portal',
+ 'version': '13.0.1.0.0',
+ 'author': "Hibou Corp.",
+ 'category': 'Sale',
+ 'license': 'AGPL-3',
+ 'website': "https://hibou.io",
+ 'description': """
+Display sale exceptions on customer portal and prevent further action
+""",
+ 'depends': [
+ 'sale_exception',
+ ],
+ 'demo': [],
+ 'data': [
+ 'views/sale_portal_templates.xml',
+ 'views/sale_views.xml',
+ ],
+ 'auto_install': False,
+ 'installable': True,
+}
diff --git a/sale_exception_portal/models/__init__.py b/sale_exception_portal/models/__init__.py
new file mode 100644
index 00000000..8a0dc04e
--- /dev/null
+++ b/sale_exception_portal/models/__init__.py
@@ -0,0 +1 @@
+from . import sale
diff --git a/sale_exception_portal/models/sale.py b/sale_exception_portal/models/sale.py
new file mode 100644
index 00000000..ae37d0b8
--- /dev/null
+++ b/sale_exception_portal/models/sale.py
@@ -0,0 +1,35 @@
+import time
+from odoo import fields, models
+
+
+class ExceptionRule(models.Model):
+ _inherit = 'exception.rule'
+
+ website_description = fields.Text('Description for Website')
+
+
+class SaleOrder(models.Model):
+ _inherit = 'sale.order'
+
+ def _check_sale_order_exceptions(self):
+ so_exceptions = self.env['exception.rule'].search([('active', '=', True),
+ ('model', '=', 'sale.order'),
+ ('exception_type', '=', 'by_py_code')])
+
+ reasons = []
+
+ for ex in so_exceptions:
+ # Globals won't expose modules used in exception rules python code.
+ # They will have to be manually passed through params. ex [time]
+ # Locals() can be used instead of defined params, but can also cause buggy behavior on return
+ params = {'sale': self, 'exception': ex, 'time': time}
+ try:
+ exec(ex.code, globals(), params)
+ if 'failed' in params:
+ desc = ex.website_description or ex.description
+ message = {'title': ex.name, 'description': desc}
+ reasons.append(message)
+ except:
+ pass
+
+ return reasons
diff --git a/sale_exception_portal/views/sale_portal_templates.xml b/sale_exception_portal/views/sale_portal_templates.xml
new file mode 100644
index 00000000..58d680ec
--- /dev/null
+++ b/sale_exception_portal/views/sale_portal_templates.xml
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+ Sign & Pay
+ Accept & Sign
+
+
+
+ Accept & Pay
+ Pay Now
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sale_exception_portal/views/sale_views.xml b/sale_exception_portal/views/sale_views.xml
new file mode 100644
index 00000000..e228739a
--- /dev/null
+++ b/sale_exception_portal/views/sale_views.xml
@@ -0,0 +1,15 @@
+
+
+
+
+ exception.rule.form.inherit
+ exception.rule
+
+
+
+
+
+
+
+
+