Initial commit: Odoo 18.0-20251222 extra-addons
Some checks failed
pre-commit / pre-commit (push) Has been cancelled
tests / Detect unreleased dependencies (push) Has been cancelled
tests / test with OCB (push) Has been cancelled
tests / test with Odoo (push) Has been cancelled

This commit is contained in:
tocmo0nlord
2026-03-13 20:43:25 +00:00
parent 36e847a7df
commit adbe430761
9472 changed files with 1265727 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
from . import sale_order_split_strategy
from . import sale_order

View 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()

View 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)]])