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
+
+
+
+
+
+
+
+
+
diff --git a/sale_exception_user/__init__.py b/sale_exception_user/__init__.py
new file mode 100644
index 00000000..455a4c33
--- /dev/null
+++ b/sale_exception_user/__init__.py
@@ -0,0 +1,3 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from . import wizard
diff --git a/sale_exception_user/__manifest__.py b/sale_exception_user/__manifest__.py
new file mode 100644
index 00000000..d41c61db
--- /dev/null
+++ b/sale_exception_user/__manifest__.py
@@ -0,0 +1,23 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+{
+ 'name': 'Sale Exception Rule User',
+ 'version': '16.0.1.0.0',
+ 'author': 'Hibou Corp.',
+ 'license': 'OPL-1',
+ 'category': 'Generic Modules',
+ 'summary': 'Allow users to ignore sale exceptions',
+ 'description': """
+Allow users to ignore sale exceptions
+""",
+ 'website': 'https://hibou.io/',
+ 'depends': [
+ 'base_exception_user',
+ 'sale_exception',
+ ],
+ 'data': [
+ 'wizard/sale_exception_confirm_view.xml',
+ ],
+ 'installable': True,
+ 'auto_install': True,
+}
diff --git a/sale_exception_user/tests/__init__.py b/sale_exception_user/tests/__init__.py
new file mode 100644
index 00000000..e236a89d
--- /dev/null
+++ b/sale_exception_user/tests/__init__.py
@@ -0,0 +1,3 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from . import test_sale_exception_user
diff --git a/sale_exception_user/tests/test_sale_exception_user.py b/sale_exception_user/tests/test_sale_exception_user.py
new file mode 100644
index 00000000..4033df8d
--- /dev/null
+++ b/sale_exception_user/tests/test_sale_exception_user.py
@@ -0,0 +1,59 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from odoo.tests import Form, TransactionCase
+
+
+class TestSaleException(TransactionCase):
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ groups = (cls.env.ref('base_exception_user.group_exception_rule_user') |
+ cls.env.ref('sales_team.group_sale_manager'))
+ user_dict = {
+ "name": "User test",
+ "login": "tua@example.com",
+ "password": "base-test-passwd",
+ "email": "armande.hruser@example.com",
+ "groups_id": [(6, 0, groups.ids)],
+ }
+ cls.user_test = cls.env['res.users'].create(user_dict)
+ cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True))
+
+ def test_sale_exception_user(self):
+ with self.with_user(self.user_test.login):
+ exception = self.env.ref("sale_exception.excep_no_zip").sudo()
+ exception.active = True
+ exception.allow_user_ignore = True
+
+ partner = self.env.ref("base.res_partner_1")
+ partner.zip = False
+ p = self.env.ref("product.product_product_6")
+ so1 = self.env["sale.order"].create(
+ {
+ "partner_id": partner.id,
+ "partner_invoice_id": partner.id,
+ "partner_shipping_id": partner.id,
+ "order_line": [
+ (
+ 0,
+ 0,
+ {
+ "name": p.name,
+ "product_id": p.id,
+ "product_uom_qty": 2,
+ "product_uom": p.uom_id.id,
+ "price_unit": p.list_price,
+ },
+ )
+ ],
+ "pricelist_id": self.env.ref("product.list0").id,
+ }
+ )
+
+ action = so1.action_confirm()
+ wizard = Form(self.env[action['res_model']].with_user(self.user_test).with_context(action['context'])).save()
+ self.assertTrue(wizard.show_ignore_button)
+ action = wizard.action_ignore()
+ self.assertEqual(action.get('type'), 'ir.actions.act_window_close')
+ self.assertTrue(so1.ignore_exception)
+ self.assertEqual(so1.state, 'sale')
diff --git a/sale_exception_user/wizard/__init__.py b/sale_exception_user/wizard/__init__.py
new file mode 100644
index 00000000..5d468fa7
--- /dev/null
+++ b/sale_exception_user/wizard/__init__.py
@@ -0,0 +1,3 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from . import sale_exception_confirm
diff --git a/sale_exception_user/wizard/sale_exception_confirm.py b/sale_exception_user/wizard/sale_exception_confirm.py
new file mode 100644
index 00000000..b096f54b
--- /dev/null
+++ b/sale_exception_user/wizard/sale_exception_confirm.py
@@ -0,0 +1,18 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from odoo import fields, models
+
+
+class SaleExceptionConfirm(models.TransientModel):
+ _inherit = 'sale.exception.confirm'
+
+ def action_confirm(self):
+ res = super().action_confirm()
+ if self.ignore:
+ self.related_model_id.action_confirm()
+ return res
+
+ def _action_ignore(self):
+ self.related_model_id.ignore_exception = True
+ self.related_model_id.action_confirm()
+ return super()._action_ignore()
diff --git a/sale_exception_user/wizard/sale_exception_confirm_view.xml b/sale_exception_user/wizard/sale_exception_confirm_view.xml
new file mode 100644
index 00000000..d3a7bbf2
--- /dev/null
+++ b/sale_exception_user/wizard/sale_exception_confirm_view.xml
@@ -0,0 +1,18 @@
+
+
+
+
+ Sale Exceptions User
+ sale.exception.confirm
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sale_exception_website/__init__.py b/sale_exception_website/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/sale_exception_website/__manifest__.py b/sale_exception_website/__manifest__.py
new file mode 100644
index 00000000..10e9c6cf
--- /dev/null
+++ b/sale_exception_website/__manifest__.py
@@ -0,0 +1,22 @@
+{
+ 'name': 'Sale Exception Website',
+ 'summary': 'Display sale exceptions on eCommerce site',
+ 'version': '16.0.1.0.0',
+ 'author': "Hibou Corp.",
+ 'category': 'Sale',
+ 'license': 'AGPL-3',
+ 'website': "https://hibou.io",
+ 'description': """
+Display sale exceptions on eCommerce site and prevent purchases
+""",
+ 'depends': [
+ 'sale_exception_portal',
+ 'website_sale',
+ ],
+ 'demo': [],
+ 'data': [
+ 'views/website_templates.xml',
+ ],
+ 'auto_install': False,
+ 'installable': True,
+}
diff --git a/sale_exception_website/views/website_templates.xml b/sale_exception_website/views/website_templates.xml
new file mode 100644
index 00000000..efe369b6
--- /dev/null
+++ b/sale_exception_website/views/website_templates.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+ not order._check_sale_order_exceptions()
+
+
+
+
+
+
+
+
+
+
+
+