Initial commit: Odoo 18.0-20251222 extra-addons
This commit is contained in:
2
sale_order_split_strategy/models/__init__.py
Executable file
2
sale_order_split_strategy/models/__init__.py
Executable file
@@ -0,0 +1,2 @@
|
||||
from . import sale_order_split_strategy
|
||||
from . import sale_order
|
||||
159
sale_order_split_strategy/models/sale_order.py
Executable file
159
sale_order_split_strategy/models/sale_order.py
Executable file
@@ -0,0 +1,159 @@
|
||||
# Copyright 2024 Camptocamp SA
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
||||
from collections import defaultdict
|
||||
|
||||
from odoo import fields, models
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class SaleOrder(models.Model):
|
||||
_inherit = "sale.order"
|
||||
|
||||
split_strategy_id = fields.Many2one(
|
||||
comodel_name="sale.order.split.strategy",
|
||||
help="The strategy that will be used to split the sales order",
|
||||
)
|
||||
|
||||
def action_split(self, silent_errors=False):
|
||||
orders_without_split = self.filtered(lambda o: not o.split_strategy_id)
|
||||
if not silent_errors and orders_without_split:
|
||||
raise UserError(
|
||||
self.env._(
|
||||
"Cannot split orders %(order_names)s "
|
||||
"without any split strategy defined",
|
||||
order_names=", ".join(orders_without_split.mapped("name")),
|
||||
)
|
||||
)
|
||||
new_order_ids = []
|
||||
for order in self:
|
||||
lines_to_split = order.split_strategy_id._select_lines_to_split(order)
|
||||
if order._has_only_lines_to_split(lines_to_split):
|
||||
order._handle_only_lines_to_split()
|
||||
continue
|
||||
if not lines_to_split:
|
||||
order._handle_no_lines_to_split(silent_errors=silent_errors)
|
||||
continue
|
||||
new_order = order.copy(order._prepare_order_split_copy_defaults())
|
||||
sections_dict = order._get_lines_grouped_by_sections()
|
||||
order._split_lines(sections_dict, lines_to_split, new_order)
|
||||
order.message_post(body=order._prepare_message_split_to(new_order))
|
||||
new_order.message_post(body=new_order._prepare_message_split_from(order))
|
||||
new_order_ids.append(new_order.id)
|
||||
order._postprocess_split_from()
|
||||
new_order._postprocess_split_to(order)
|
||||
return self.browse(new_order_ids)
|
||||
|
||||
def _handle_no_lines_to_split(self, silent_errors=False):
|
||||
self.ensure_one()
|
||||
msg = self.env._(
|
||||
"Cannot split order %(order_name)s according to its strategy"
|
||||
" because there are no matching lines",
|
||||
order_name=self.name,
|
||||
)
|
||||
if not silent_errors:
|
||||
raise UserError(msg)
|
||||
else:
|
||||
self.message_post(body=msg)
|
||||
|
||||
def _handle_only_lines_to_split(self):
|
||||
self.ensure_one()
|
||||
self.message_post(
|
||||
body=self.env._(
|
||||
"This sale order was not split using strategy %(strategy)s"
|
||||
" because there would not be any lines left on this order.",
|
||||
strategy=self.split_strategy_id.name,
|
||||
)
|
||||
)
|
||||
|
||||
def _has_only_lines_to_split(self, lines_to_split):
|
||||
self.ensure_one()
|
||||
return (
|
||||
self.order_line.filtered(lambda line: not line._is_delivery())
|
||||
== lines_to_split
|
||||
)
|
||||
|
||||
def _split_lines(self, sections_dict, lines_to_split, target_order):
|
||||
self.ensure_one()
|
||||
copy_sections = self.split_strategy_id.copy_sections
|
||||
copy_notes = self.split_strategy_id.copy_notes
|
||||
for section_id, line_ids in sections_dict.items():
|
||||
section_line = self.env["sale.order.line"].browse(section_id)
|
||||
section_lines = self.env["sale.order.line"].browse(line_ids)
|
||||
for line in section_lines:
|
||||
if line in lines_to_split:
|
||||
line.write(target_order._prepare_order_split_line_move_defaults())
|
||||
# FIXME: We should consider if the line is part of the section
|
||||
if copy_notes and line.display_type == "line_note":
|
||||
line.copy(target_order._prepare_order_split_line_move_defaults())
|
||||
lines_in_section_to_split = [
|
||||
li in lines_to_split for li in section_lines if not li.display_type
|
||||
]
|
||||
# If all lines in section are to split, move the section to split order
|
||||
if copy_sections and section_line and all(lines_in_section_to_split):
|
||||
section_line.write(
|
||||
target_order._prepare_order_split_line_move_defaults()
|
||||
)
|
||||
# If any line in section is to split, copy the section to split order
|
||||
elif copy_sections and section_line and any(lines_in_section_to_split):
|
||||
section_line.copy(
|
||||
target_order._prepare_order_split_line_move_defaults()
|
||||
)
|
||||
return True
|
||||
|
||||
def _get_lines_grouped_by_sections(self):
|
||||
# Prepare a dict from sale order lines and their sections
|
||||
self.ensure_one()
|
||||
sections_dict = defaultdict(list)
|
||||
section_id = None
|
||||
for line in self.order_line.sorted():
|
||||
if line.display_type == "line_section":
|
||||
section_id = line.id
|
||||
sections_dict[section_id].append(line.id)
|
||||
return sections_dict
|
||||
|
||||
def _prepare_message_split_from(self, order):
|
||||
self.ensure_one()
|
||||
return self.env["ir.ui.view"]._render_template(
|
||||
"sale_order_split_strategy.split_from", values={"from_order": order}
|
||||
)
|
||||
|
||||
def _prepare_message_split_to(self, order):
|
||||
self.ensure_one()
|
||||
return self.env["ir.ui.view"]._render_template(
|
||||
"sale_order_split_strategy.split_to",
|
||||
values={"from_order": self, "to_order": order},
|
||||
)
|
||||
|
||||
def _prepare_order_split_copy_defaults(self):
|
||||
"""Hook to customize values used on new sale order from split"""
|
||||
self.ensure_one()
|
||||
return {
|
||||
"order_line": False,
|
||||
"client_order_ref": self.client_order_ref,
|
||||
"reference": self.reference,
|
||||
"commitment_date": self.commitment_date,
|
||||
}
|
||||
|
||||
def _prepare_order_split_line_move_defaults(self):
|
||||
"""Hook to customize values used on new sale order line from split"""
|
||||
self.ensure_one()
|
||||
return {"order_id": self.id}
|
||||
|
||||
def _postprocess_split_from(self):
|
||||
self.ensure_one()
|
||||
# Remove empty sections
|
||||
if (
|
||||
not self.split_strategy_id.copy_sections
|
||||
and self.split_strategy_id.remove_empty_sections_after_split
|
||||
):
|
||||
sections_dict = self._get_lines_grouped_by_sections()
|
||||
section_ids_to_unlink = [
|
||||
section_id
|
||||
for section_id, line_ids in sections_dict.items()
|
||||
if len(line_ids) == 1 and section_id == line_ids[0]
|
||||
]
|
||||
section_lines = self.env["sale.order.line"].browse(section_ids_to_unlink)
|
||||
section_lines.unlink()
|
||||
|
||||
def _postprocess_split_to(self, origin_order):
|
||||
self.ensure_one()
|
||||
35
sale_order_split_strategy/models/sale_order_split_strategy.py
Executable file
35
sale_order_split_strategy/models/sale_order_split_strategy.py
Executable file
@@ -0,0 +1,35 @@
|
||||
# Copyright 2024 Camptocamp SA
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
||||
from odoo import fields, models
|
||||
from odoo.osv.expression import AND
|
||||
from odoo.tools.safe_eval import safe_eval
|
||||
|
||||
|
||||
class SaleOrderSplitStrategy(models.Model):
|
||||
_name = "sale.order.split.strategy"
|
||||
_description = "Order split strategy"
|
||||
_order = "sequence,name"
|
||||
|
||||
name = fields.Char(required=True)
|
||||
sequence = fields.Integer(default=10)
|
||||
line_filter_id = fields.Many2one(
|
||||
"ir.filters",
|
||||
domain="[('model_id', '=', 'sale.order.line')]",
|
||||
required=True,
|
||||
)
|
||||
copy_sections = fields.Boolean()
|
||||
remove_empty_sections_after_split = fields.Boolean()
|
||||
copy_notes = fields.Boolean()
|
||||
|
||||
def _select_lines_to_split(self, orders):
|
||||
self.ensure_one()
|
||||
domain = self._get_lines_to_split_domain(orders)
|
||||
return (
|
||||
self.env["sale.order.line"]
|
||||
.search(domain)
|
||||
.filtered(lambda li: not li._is_delivery())
|
||||
)
|
||||
|
||||
def _get_lines_to_split_domain(self, orders):
|
||||
domain = safe_eval(self.line_filter_id.domain)
|
||||
return AND([domain, [("order_id", "in", orders.ids)]])
|
||||
Reference in New Issue
Block a user