diff --git a/base_exception_user/__init__.py b/base_exception_user/__init__.py
new file mode 100644
index 00000000..9b429614
--- /dev/null
+++ b/base_exception_user/__init__.py
@@ -0,0 +1,2 @@
+from . import models
+from . import wizard
diff --git a/base_exception_user/__manifest__.py b/base_exception_user/__manifest__.py
new file mode 100644
index 00000000..ec5728b4
--- /dev/null
+++ b/base_exception_user/__manifest__.py
@@ -0,0 +1,24 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+{
+ 'name': 'Exception Rule User',
+ 'version': '12.0.1.0.0',
+ 'author': 'Hibou Corp.',
+ 'license': 'OPL-1',
+ 'category': 'Generic Modules',
+ 'summary': 'Allow users to ignore exceptions',
+ 'description': """
+Allow users to ignore exceptions
+""",
+ 'website': 'https://hibou.io/',
+ 'depends': [
+ 'base_exception',
+ ],
+ 'data': [
+ 'security/base_exception_security.xml',
+ 'views/base_exception_views.xml',
+ 'wizard/base_exception_confirm_view.xml',
+ ],
+ 'installable': True,
+ 'auto_install': False,
+}
diff --git a/base_exception_user/models/__init__.py b/base_exception_user/models/__init__.py
new file mode 100644
index 00000000..495e1fe2
--- /dev/null
+++ b/base_exception_user/models/__init__.py
@@ -0,0 +1 @@
+from . import base_exception
diff --git a/base_exception_user/models/base_exception.py b/base_exception_user/models/base_exception.py
new file mode 100644
index 00000000..46453bce
--- /dev/null
+++ b/base_exception_user/models/base_exception.py
@@ -0,0 +1,7 @@
+from odoo import models, fields
+
+
+class ExceptionRule(models.Model):
+ _inherit = 'exception.rule'
+
+ allow_user_ignore = fields.Boolean('Allow User Ignore')
diff --git a/base_exception_user/security/base_exception_security.xml b/base_exception_user/security/base_exception_security.xml
new file mode 100644
index 00000000..0dfbf591
--- /dev/null
+++ b/base_exception_user/security/base_exception_security.xml
@@ -0,0 +1,13 @@
+
+
+
+
+ Exception user
+
+
+
+
+
+
+
+
diff --git a/base_exception_user/views/base_exception_views.xml b/base_exception_user/views/base_exception_views.xml
new file mode 100644
index 00000000..08e5f6b1
--- /dev/null
+++ b/base_exception_user/views/base_exception_views.xml
@@ -0,0 +1,26 @@
+
+
+
+
+ exception.rule.tree.inherit.user
+ exception.rule
+
+
+
+
+
+
+
+
+
+ exception.rule.form.inherit.user
+ exception.rule
+
+
+
+
+
+
+
+
+
diff --git a/base_exception_user/wizard/__init__.py b/base_exception_user/wizard/__init__.py
new file mode 100644
index 00000000..a31f679e
--- /dev/null
+++ b/base_exception_user/wizard/__init__.py
@@ -0,0 +1 @@
+from . import base_exception_confirm
diff --git a/base_exception_user/wizard/base_exception_confirm.py b/base_exception_user/wizard/base_exception_confirm.py
new file mode 100644
index 00000000..80b18073
--- /dev/null
+++ b/base_exception_user/wizard/base_exception_confirm.py
@@ -0,0 +1,36 @@
+import html
+from odoo import api, fields, models
+
+
+class ExceptionRuleConfirm(models.AbstractModel):
+ _inherit = 'exception.rule.confirm'
+
+ show_ignore_button = fields.Boolean('Allow User Ignore', compute='_compute_show_ignore_button')
+
+ @api.depends('exception_ids')
+ def _compute_show_ignore_button(self):
+ for wiz in self:
+ wiz.show_ignore_button = (self.env.user.has_group('base_exception_user.group_exception_rule_user') and
+ all(wiz.exception_ids.mapped('allow_user_ignore')))
+
+ def action_confirm(self):
+ if self.ignore and 'message_ids' in self.related_model_id:
+ exceptions_summary = '
' % ''.join(
+ ['%s: %s' % tuple(map(html.escape, (e.name, e.description))) for e in
+ self.exception_ids])
+ msg = 'Exceptions ignored:
' + exceptions_summary
+ self.related_model_id.message_post(body=msg)
+ return super().action_confirm()
+
+
+ def action_ignore(self):
+ self.ensure_one()
+ if self.show_ignore_button:
+ if 'message_ids' in self.related_model_id:
+ msg = 'Exceptions ignored:
' + self.related_model_id.exceptions_summary
+ self.related_model_id.message_post(body=msg)
+ return self._action_ignore()
+ return False
+
+ def _action_ignore(self):
+ return {'type': 'ir.actions.act_window_close'}
diff --git a/base_exception_user/wizard/base_exception_confirm_view.xml b/base_exception_user/wizard/base_exception_confirm_view.xml
new file mode 100644
index 00000000..4252ff15
--- /dev/null
+++ b/base_exception_user/wizard/base_exception_confirm_view.xml
@@ -0,0 +1,18 @@
+
+
+
+
+ Exceptions Rules
+ exception.rule.confirm
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sale_exception_user/__init__.py b/sale_exception_user/__init__.py
new file mode 100644
index 00000000..40272379
--- /dev/null
+++ b/sale_exception_user/__init__.py
@@ -0,0 +1 @@
+from . import wizard
diff --git a/sale_exception_user/__manifest__.py b/sale_exception_user/__manifest__.py
new file mode 100644
index 00000000..e4d737d4
--- /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': '12.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/wizard/__init__.py b/sale_exception_user/wizard/__init__.py
new file mode 100644
index 00000000..fcf2f6c2
--- /dev/null
+++ b/sale_exception_user/wizard/__init__.py
@@ -0,0 +1 @@
+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..9e52951e
--- /dev/null
+++ b/sale_exception_user/wizard/sale_exception_confirm.py
@@ -0,0 +1,16 @@
+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/stock_exception/__init__.py b/stock_exception/__init__.py
new file mode 100644
index 00000000..c7120225
--- /dev/null
+++ b/stock_exception/__init__.py
@@ -0,0 +1,4 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from . import models
+from . import wizard
diff --git a/stock_exception/__manifest__.py b/stock_exception/__manifest__.py
new file mode 100644
index 00000000..b4a21d6e
--- /dev/null
+++ b/stock_exception/__manifest__.py
@@ -0,0 +1,27 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+{
+ 'name': 'Stock Exception Rule',
+ 'version': '12.0.1.0.0',
+ 'author': 'Hibou Corp.',
+ 'license': 'OPL-1',
+ 'category': 'Generic Modules',
+ 'summary': 'Custom exceptions on delivery orders',
+ 'description': """
+Custom exceptions on delivery orders
+""",
+ 'website': 'https://hibou.io/',
+ 'depends': [
+ 'base_exception_user',
+ 'stock',
+ ],
+ 'data': [
+ 'views/stock_views.xml',
+ 'wizard/stock_exception_confirm_views.xml',
+ ],
+ 'demo': [
+ 'demo/stock_exception_demo.xml',
+ ],
+ 'installable': True,
+ 'auto_install': False,
+}
diff --git a/stock_exception/demo/stock_exception_demo.xml b/stock_exception/demo/stock_exception_demo.xml
new file mode 100644
index 00000000..032968c5
--- /dev/null
+++ b/stock_exception/demo/stock_exception_demo.xml
@@ -0,0 +1,13 @@
+
+
+
+
+ No ZIP code on destination
+ No ZIP code on destination
+ 50
+ stock.picking
+ if not picking.partner_id.zip: failed=True
+
+
+
+
diff --git a/stock_exception/models/__init__.py b/stock_exception/models/__init__.py
new file mode 100644
index 00000000..7297e4be
--- /dev/null
+++ b/stock_exception/models/__init__.py
@@ -0,0 +1,3 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from . import stock
diff --git a/stock_exception/models/stock.py b/stock_exception/models/stock.py
new file mode 100644
index 00000000..95824dfb
--- /dev/null
+++ b/stock_exception/models/stock.py
@@ -0,0 +1,42 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from odoo import api, models, fields
+
+
+class ExceptionRule(models.Model):
+ _inherit = 'exception.rule'
+
+ model = fields.Selection(
+ selection_add=[
+ ('stock.picking', 'Transfer'),
+ ]
+ )
+ picking_ids = fields.Many2many(
+ 'stock.picking',
+ string="Transfers")
+
+class Picking(models.Model):
+ _inherit = ['stock.picking', 'base.exception']
+ _name = 'stock.picking'
+ _order = 'main_exception_id asc, priority desc, date asc, id desc'
+
+ @api.model
+ def _exception_rule_eval_context(self, rec):
+ res = super(Picking, self)._exception_rule_eval_context(rec)
+ res['picking'] = rec
+ return res
+
+ @api.model
+ def _reverse_field(self):
+ return 'picking_ids'
+
+ def button_validate(self):
+ self.ensure_one()
+ if self.detect_exceptions():
+ return self._popup_exceptions()
+ return super().button_validate()
+
+ @api.model
+ def _get_popup_action(self):
+ return self.env.ref('stock_exception.action_stock_exception_confirm')
+
diff --git a/stock_exception/tests/__init__.py b/stock_exception/tests/__init__.py
new file mode 100644
index 00000000..7589231c
--- /dev/null
+++ b/stock_exception/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_stock_exception
diff --git a/stock_exception/tests/test_stock_exception.py b/stock_exception/tests/test_stock_exception.py
new file mode 100644
index 00000000..6525d607
--- /dev/null
+++ b/stock_exception/tests/test_stock_exception.py
@@ -0,0 +1,47 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from odoo.tests import common
+
+
+class TestStockException(common.TransactionCase):
+
+ def setUp(self):
+ super().setUp()
+ self.env = self.env(context=dict(self.env.context, tracking_disable=True))
+
+ def test_delivery_order_exception(self):
+ exception = self.env.ref('stock_exception.excep_no_zip')
+ exception.active = True
+ partner = self.env.ref('base.res_partner_12') # Azure Interior
+ partner.zip = False
+ p = self.env.ref('product.product_product_6')
+ stock_location = self.env.ref('stock.stock_location_stock')
+ customer_location = self.env.ref('stock.stock_location_customers')
+ self.env['stock.quant']._update_available_quantity(p, stock_location, 100)
+ delivery_order = self.env['stock.picking'].create({
+ 'partner_id': partner.id,
+ 'picking_type_id': self.ref('stock.picking_type_out'),
+ 'location_id': self.env.ref('stock.stock_location_stock').id,
+ 'location_dest_id': self.env.ref('stock.stock_location_customers').id,
+ 'move_line_ids': [(0, 0, {'product_id': p.id,
+ 'product_uom_id': p.uom_id.id,
+ 'qty_done': 3.0,
+ 'location_id': stock_location.id,
+ 'location_dest_id': customer_location.id})],
+ })
+
+ # validate delivery order
+ delivery_order.button_validate()
+ self.assertEqual(delivery_order.state, 'draft')
+
+ # Simulation the opening of the wizard sale_exception_confirm and
+ # set ignore_exception to True
+ stock_exception_confirm = self.env['stock.exception.confirm'].with_context(
+ {
+ 'active_id': delivery_order.id,
+ 'active_ids': [delivery_order.id],
+ 'active_model': delivery_order._name
+ }).create({'ignore': True})
+ stock_exception_confirm.action_confirm()
+ self.assertTrue(delivery_order.ignore_exception)
+ self.assertEqual(delivery_order.state, 'done')
diff --git a/stock_exception/views/stock_views.xml b/stock_exception/views/stock_views.xml
new file mode 100644
index 00000000..232dfafa
--- /dev/null
+++ b/stock_exception/views/stock_views.xml
@@ -0,0 +1,65 @@
+
+
+
+
+ Stock Exception Rules
+ exception.rule
+ tree,form
+
+ [('model', '=', 'stock.picking')]
+ {'active_test': False, 'default_model' : 'stock.picking'}
+
+
+
+
+
+ stock.picking.form.inherit.exception
+ stock.picking
+
+
+
+
+
There are exceptions blocking the confirmation of this Delivery Order:
+
+
+
+
+
+
+
+
+
+
+
+ stock.picking.tree.inherit.exception
+ stock.picking
+
+
+
+
+
+
+
+
+
+ stock.picking.internal.search.inherit.exception
+ stock.picking
+
+
+
+
+
+
+
+
+
+
diff --git a/stock_exception/wizard/__init__.py b/stock_exception/wizard/__init__.py
new file mode 100644
index 00000000..f2d4f882
--- /dev/null
+++ b/stock_exception/wizard/__init__.py
@@ -0,0 +1,3 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from . import stock_exception_confirm
diff --git a/stock_exception/wizard/stock_exception_confirm.py b/stock_exception/wizard/stock_exception_confirm.py
new file mode 100644
index 00000000..b575f965
--- /dev/null
+++ b/stock_exception/wizard/stock_exception_confirm.py
@@ -0,0 +1,25 @@
+# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
+
+from odoo import api, fields, models
+
+
+class StockExceptionConfirm(models.TransientModel):
+ _name = 'stock.exception.confirm'
+ _inherit = ['exception.rule.confirm']
+
+ related_model_id = fields.Many2one('stock.picking', 'Transfer')
+
+ def action_confirm(self):
+ self.ensure_one()
+ if self.ignore:
+ self.related_model_id.ignore_exception = True
+ res = super().action_confirm()
+ if self.ignore:
+ return self.related_model_id.button_validate()
+ else:
+ return res
+
+ def _action_ignore(self):
+ self.related_model_id.ignore_exception = True
+ super()._action_ignore()
+ return self.related_model_id.button_validate()
diff --git a/stock_exception/wizard/stock_exception_confirm_views.xml b/stock_exception/wizard/stock_exception_confirm_views.xml
new file mode 100644
index 00000000..0150c870
--- /dev/null
+++ b/stock_exception/wizard/stock_exception_confirm_views.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ Stock Exceptions Rules
+ stock.exception.confirm
+
+ primary
+
+
+
+
+
+
+
+
+ Blocked due to exceptions
+ ir.actions.act_window
+ stock.exception.confirm
+ form
+
+ new
+
+
+