Initial commit: Odoo 18.0-20251222 extra-addons
This commit is contained in:
6
sale_exception/models/__init__.py
Executable file
6
sale_exception/models/__init__.py
Executable file
@@ -0,0 +1,6 @@
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||
from . import exception_rule
|
||||
from . import sale_order
|
||||
from . import sale_order_line
|
||||
from . import res_company
|
||||
from . import res_config_settings
|
||||
22
sale_exception/models/exception_rule.py
Executable file
22
sale_exception/models/exception_rule.py
Executable file
@@ -0,0 +1,22 @@
|
||||
# Copyright 2011 Akretion, Sodexis
|
||||
# Copyright 2018 Akretion
|
||||
# Copyright 2019 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class ExceptionRule(models.Model):
|
||||
_inherit = "exception.rule"
|
||||
|
||||
model = fields.Selection(
|
||||
selection_add=[
|
||||
("sale.order", "Sale order"),
|
||||
("sale.order.line", "Sale order line"),
|
||||
],
|
||||
ondelete={
|
||||
"sale.order": "cascade",
|
||||
"sale.order.line": "cascade",
|
||||
},
|
||||
)
|
||||
sale_ids = fields.Many2many("sale.order", string="Sales")
|
||||
12
sale_exception/models/res_company.py
Executable file
12
sale_exception/models/res_company.py
Executable file
@@ -0,0 +1,12 @@
|
||||
# Copyright 2025 Camptocamp SA
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class ResCompany(models.Model):
|
||||
_inherit = "res.company"
|
||||
|
||||
sale_exception_show_popup = fields.Boolean(
|
||||
string="Sale Exception Popup",
|
||||
default=True,
|
||||
)
|
||||
13
sale_exception/models/res_config_settings.py
Executable file
13
sale_exception/models/res_config_settings.py
Executable file
@@ -0,0 +1,13 @@
|
||||
# Copyright 2025 Camptocamp SA
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class ResConfigSettings(models.TransientModel):
|
||||
_inherit = "res.config.settings"
|
||||
|
||||
sale_exception_show_popup = fields.Boolean(
|
||||
related="company_id.sale_exception_show_popup",
|
||||
readonly=False,
|
||||
)
|
||||
69
sale_exception/models/sale_order.py
Executable file
69
sale_exception/models/sale_order.py
Executable file
@@ -0,0 +1,69 @@
|
||||
# Copyright 2011 Akretion, Sodexis
|
||||
# Copyright 2018 Akretion
|
||||
# Copyright 2019 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from odoo import api, models
|
||||
|
||||
|
||||
class SaleOrder(models.Model):
|
||||
_inherit = ["sale.order", "base.exception"]
|
||||
_name = "sale.order"
|
||||
_order = "main_exception_id asc, date_order desc, name desc"
|
||||
|
||||
@api.model
|
||||
def _reverse_field(self):
|
||||
return "sale_ids"
|
||||
|
||||
def detect_exceptions(self):
|
||||
all_exceptions = super().detect_exceptions()
|
||||
lines = self.mapped("order_line")
|
||||
all_exceptions += lines.detect_exceptions()
|
||||
return all_exceptions
|
||||
|
||||
@api.model
|
||||
def test_all_draft_orders(self):
|
||||
order_set = self.search([("state", "=", "draft")])
|
||||
order_set.detect_exceptions()
|
||||
return True
|
||||
|
||||
def _fields_trigger_check_exception(self):
|
||||
return ["ignore_exception", "order_line", "state"]
|
||||
|
||||
def _check_sale_check_exception(self, vals):
|
||||
check_exceptions = any(
|
||||
field in vals for field in self._fields_trigger_check_exception()
|
||||
)
|
||||
if check_exceptions:
|
||||
self.sale_check_exception()
|
||||
|
||||
def write(self, vals):
|
||||
result = super().write(vals)
|
||||
self._check_sale_check_exception(vals)
|
||||
return result
|
||||
|
||||
def sale_check_exception(self):
|
||||
orders = self.filtered(lambda s: s.state == "sale")
|
||||
if orders:
|
||||
orders._check_exception()
|
||||
|
||||
def action_confirm(self):
|
||||
if self.detect_exceptions():
|
||||
if not self.env.company.sale_exception_show_popup:
|
||||
return
|
||||
return self._popup_exceptions()
|
||||
return super().action_confirm()
|
||||
|
||||
def action_draft(self):
|
||||
res = super().action_draft()
|
||||
orders = self.filtered("ignore_exception")
|
||||
orders.write({"ignore_exception": False})
|
||||
return res
|
||||
|
||||
def _sale_get_lines(self):
|
||||
self.ensure_one()
|
||||
return self.order_line
|
||||
|
||||
@api.model
|
||||
def _get_popup_action(self):
|
||||
return self.env.ref("sale_exception.action_sale_exception_confirm")
|
||||
61
sale_exception/models/sale_order_line.py
Executable file
61
sale_exception/models/sale_order_line.py
Executable file
@@ -0,0 +1,61 @@
|
||||
# © 2019 Akretion
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
import html
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo.fields import Command
|
||||
|
||||
|
||||
class SaleOrderLine(models.Model):
|
||||
_inherit = ["sale.order.line", "base.exception.method"]
|
||||
_name = "sale.order.line"
|
||||
|
||||
exception_ids = fields.Many2many(
|
||||
"exception.rule", string="Exceptions", copy=False, readonly=True
|
||||
)
|
||||
exceptions_summary = fields.Html(
|
||||
readonly=True, compute="_compute_exceptions_summary"
|
||||
)
|
||||
ignore_exception = fields.Boolean(
|
||||
related="order_id.ignore_exception", store=True, string="Ignore Exceptions"
|
||||
)
|
||||
is_exception_danger = fields.Boolean(compute="_compute_is_exception_danger")
|
||||
|
||||
@api.depends("exception_ids", "ignore_exception")
|
||||
def _compute_is_exception_danger(self):
|
||||
for rec in self:
|
||||
rec.is_exception_danger = (
|
||||
len(rec.exception_ids) > 0 and not rec.ignore_exception
|
||||
)
|
||||
|
||||
@api.depends("exception_ids", "ignore_exception")
|
||||
def _compute_exceptions_summary(self):
|
||||
for rec in self:
|
||||
if rec.exception_ids and not rec.ignore_exception:
|
||||
rec.exceptions_summary = rec._get_exception_summary()
|
||||
else:
|
||||
rec.exceptions_summary = False
|
||||
|
||||
def _get_exception_summary(self):
|
||||
items = "".join(
|
||||
f"<li>{html.escape(e.name)}: <i>{html.escape(e.description)}</i></li>"
|
||||
for e in self.exception_ids
|
||||
)
|
||||
return f"<ul>{items}</ul>"
|
||||
|
||||
def _get_main_records(self):
|
||||
return self.mapped("order_id")
|
||||
|
||||
@api.model
|
||||
def _reverse_field(self):
|
||||
return "sale_ids"
|
||||
|
||||
def _detect_exceptions(self, rule):
|
||||
records = super()._detect_exceptions(rule)
|
||||
# Thanks to the new flush of odoo 13.0, queries will be optimized
|
||||
# together at the end even if we update the exception_ids many times.
|
||||
# On previous versions, this could be unoptimized.
|
||||
(self - records).exception_ids = [Command.unlink(rule.id)]
|
||||
records.exception_ids = [Command.link(rule.id)]
|
||||
return records.mapped("order_id")
|
||||
Reference in New Issue
Block a user