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..79476897
--- /dev/null
+++ b/sale_exception_portal/__manifest__.py
@@ -0,0 +1,22 @@
+{
+ 'name': 'Sale Exception Portal',
+ 'summary': 'Display sale exceptions on customer portal',
+ 'version': '16.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..76874b3c
--- /dev/null
+++ b/sale_exception_portal/models/sale.py
@@ -0,0 +1,17 @@
+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):
+ exception_ids = self.detect_exceptions()
+ exceptions = self.env['exception.rule'].browse(exception_ids)
+ reasons = [{'title': ex.name, 'description': ex.website_description or ex.description} for ex in exceptions]
+ return reasons
diff --git a/sale_exception_portal/tests/__init__.py b/sale_exception_portal/tests/__init__.py
new file mode 100644
index 00000000..0098975e
--- /dev/null
+++ b/sale_exception_portal/tests/__init__.py
@@ -0,0 +1 @@
+from . import test_check_so_exceptions
diff --git a/sale_exception_portal/tests/test_check_so_exceptions.py b/sale_exception_portal/tests/test_check_so_exceptions.py
new file mode 100644
index 00000000..7c0c3bae
--- /dev/null
+++ b/sale_exception_portal/tests/test_check_so_exceptions.py
@@ -0,0 +1,43 @@
+from odoo.tests.common import TransactionCase
+
+
+class TestCheckSOExceptions(TransactionCase):
+ def setUp(self):
+ super(TestCheckSOExceptions, self).setUp()
+
+ self.azure_customer = self.browse_ref('base.res_partner_12')
+
+ self.exception_rule = self.env['exception.rule'].create({
+ 'name': 'No Azure',
+ 'description': 'No sales to Azure',
+ 'active': True,
+ 'model': 'sale.order',
+ 'exception_type': 'by_py_code',
+ 'code': 'failed = object.partner_id and object.partner_id.id == %d' % self.azure_customer.id
+ })
+
+ self.sale_product = self.browse_ref('product.product_product_5')
+ self.sale_product.standard_price = 100.0
+
+ def test_00_check_so_exceptions(self):
+ sale_order = self.env['sale.order'].create({
+ 'partner_id': self.azure_customer.id,
+ 'order_line': [(0, 0, {
+ 'product_id': self.sale_product.id,
+ 'product_uom_qty': 1.0,
+ 'price_unit': 50.0,
+ })],
+ })
+
+ exceptions = sale_order._check_sale_order_exceptions()
+ self.assertEqual(len(exceptions), 1)
+ self.assertEqual(exceptions[0].get('description'), 'No sales to Azure')
+
+ self.exception_rule.website_description = 'Different message for website'
+ exceptions = sale_order._check_sale_order_exceptions()
+ self.assertEqual(len(exceptions), 1)
+ self.assertEqual(exceptions[0].get('description'), 'Different message for website')
+
+ self.exception_rule.active = False
+ exceptions = sale_order._check_sale_order_exceptions()
+ self.assertEqual(len(exceptions), 0)
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..a3f7434b
--- /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
+
+
+
+
+
+
+
+
+