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,105 @@
=========================
Sale Order Split Strategy
=========================
..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:6b3dde9afa8ab7731de990ae24b9dc07d8d2fa407d4ffa31030a6bdc676312cd
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png
:target: https://odoo-community.org/page/development-status
:alt: Alpha
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fsale--workflow-lightgray.png?logo=github
:target: https://github.com/OCA/sale-workflow/tree/18.0/sale_order_split_strategy
:alt: OCA/sale-workflow
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/sale-workflow-18-0/sale-workflow-18-0-sale_order_split_strategy
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/sale-workflow&target_branch=18.0
:alt: Try me on Runboat
|badge1| |badge2| |badge3| |badge4| |badge5|
This module provides the possibility to define split strategies to be
applied on sales order for splitting.
This is useful (but not only) when a sales order from external sources
(webshop, edi) includes products that need to be shipped by different
standards (temperature/environment) and so with different carriers.
.. IMPORTANT::
This is an alpha version, the data model and design can change at any time without warning.
Only for development or testing purpose, do not use in production.
`More details on development status <https://odoo-community.org/page/development-status>`_
**Table of contents**
.. contents::
:local:
Usage
=====
To use this module you first need to create splitting strategies to
apply on sales order.
::
Sales -> Configuration -> Split Startegies
The splitting is based on an ir.filter applied on sale.order.line.
Then on sales order, the Split Strategy field allows to select the
corresponding strategy. After which a button ``Split`` is available on
the form to split an order.
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/sale-workflow/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/sale-workflow/issues/new?body=module:%20sale_order_split_strategy%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
-------
* Camptocamp
Maintainers
-----------
This module is maintained by the OCA.
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
.. |maintainer-grindtildeath| image:: https://github.com/grindtildeath.png?size=40px
:target: https://github.com/grindtildeath
:alt: grindtildeath
Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:
|maintainer-grindtildeath|
This module is part of the `OCA/sale-workflow <https://github.com/OCA/sale-workflow/tree/18.0/sale_order_split_strategy>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

@@ -0,0 +1 @@
from . import models

View File

@@ -0,0 +1,22 @@
# Copyright 2024 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
{
"name": "Sale Order Split Strategy",
"summary": "Define strategies to split sales orders",
"version": "18.0.1.0.0",
"development_status": "Alpha",
"category": "Sales Management",
"website": "https://github.com/OCA/sale-workflow",
"author": "Camptocamp, Odoo Community Association (OCA)",
"maintainers": ["grindtildeath"],
"license": "AGPL-3",
"depends": [
"sale",
],
"data": [
"security/ir.model.access.csv",
"views/mail_message_template.xml",
"views/sale_order_split_strategy.xml",
"views/sale_order.xml",
],
}

View File

@@ -0,0 +1,164 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * sale_order_split_strategy
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 14.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-03-03 11:06+0000\n"
"Last-Translator: mymage <stefano.consolaro@mymage.it>\n"
"Language-Team: none\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.10.2\n"
#. module: sale_order_split_strategy
#: code:addons/sale_order_split_strategy/models/sale_order.py:0
#, python-format
msgid ""
"Cannot split order %(order_name)s according to its strategy because there "
"are no matching lines"
msgstr ""
"Non è possibile dividere l'ordine %(order_name)s in accordo alla sua "
"strategia perché non ci sono righe corrispondenti"
#. module: sale_order_split_strategy
#: code:addons/sale_order_split_strategy/models/sale_order.py:0
#, python-format
msgid "Cannot split orders %(order_names)s without any split strategy defined"
msgstr ""
"Non si può dividere l'ordine %(order_names)s senza una strategia di "
"suddivisione definita"
#. module: sale_order_split_strategy
#: model:ir.model.fields,field_description:sale_order_split_strategy.field_sale_order_split_strategy__copy_notes
msgid "Copy Notes"
msgstr "Copia note"
#. module: sale_order_split_strategy
#: model:ir.model.fields,field_description:sale_order_split_strategy.field_sale_order_split_strategy__copy_sections
msgid "Copy Sections"
msgstr "Copia sezioni"
#. module: sale_order_split_strategy
#: model:ir.model.fields,field_description:sale_order_split_strategy.field_sale_order_split_strategy__create_uid
msgid "Created by"
msgstr "Creato da"
#. module: sale_order_split_strategy
#: model:ir.model.fields,field_description:sale_order_split_strategy.field_sale_order_split_strategy__create_date
msgid "Created on"
msgstr "Creato il"
#. module: sale_order_split_strategy
#: model:ir.model.fields,field_description:sale_order_split_strategy.field_sale_order__display_name
#: model:ir.model.fields,field_description:sale_order_split_strategy.field_sale_order_split_strategy__display_name
msgid "Display Name"
msgstr "Nome visualizzato"
#. module: sale_order_split_strategy
#: model:ir.model.fields,field_description:sale_order_split_strategy.field_sale_order__id
#: model:ir.model.fields,field_description:sale_order_split_strategy.field_sale_order_split_strategy__id
msgid "ID"
msgstr "ID"
#. module: sale_order_split_strategy
#: model:ir.model.fields,field_description:sale_order_split_strategy.field_sale_order____last_update
#: model:ir.model.fields,field_description:sale_order_split_strategy.field_sale_order_split_strategy____last_update
msgid "Last Modified on"
msgstr "Ultima modifica il"
#. module: sale_order_split_strategy
#: model:ir.model.fields,field_description:sale_order_split_strategy.field_sale_order_split_strategy__write_uid
msgid "Last Updated by"
msgstr "Ultimo aggiornamento di"
#. module: sale_order_split_strategy
#: model:ir.model.fields,field_description:sale_order_split_strategy.field_sale_order_split_strategy__write_date
msgid "Last Updated on"
msgstr "Ultimo aggiornamento il"
#. module: sale_order_split_strategy
#: model:ir.model.fields,field_description:sale_order_split_strategy.field_sale_order_split_strategy__line_filter_id
msgid "Line Filter"
msgstr "Filtro riga"
#. module: sale_order_split_strategy
#: model:ir.model.fields,field_description:sale_order_split_strategy.field_sale_order_split_strategy__name
msgid "Name"
msgstr "Nome"
#. module: sale_order_split_strategy
#: model:ir.model,name:sale_order_split_strategy.model_sale_order_split_strategy
msgid "Order split strategy"
msgstr "Ordine strategia suddivisione"
#. module: sale_order_split_strategy
#: model:ir.model.fields,field_description:sale_order_split_strategy.field_sale_order_split_strategy__remove_empty_sections_after_split
msgid "Remove Empty Sections After Split"
msgstr "Rimuovere sezioni vuote dopo la divisione"
#. module: sale_order_split_strategy
#: model:ir.model,name:sale_order_split_strategy.model_sale_order
msgid "Sales Order"
msgstr "Ordine di vendita"
#. module: sale_order_split_strategy
#: model:ir.model.fields,field_description:sale_order_split_strategy.field_sale_order_split_strategy__sequence
msgid "Sequence"
msgstr "Sequenza"
#. module: sale_order_split_strategy
#: model_terms:ir.ui.view,arch_db:sale_order_split_strategy.view_order_form
msgid "Split"
msgstr "Dividi"
#. module: sale_order_split_strategy
#: model:ir.actions.act_window,name:sale_order_split_strategy.sale_order_split_strategy_action
#: model:ir.ui.menu,name:sale_order_split_strategy.sale_order_split_strategy_menu
#: model_terms:ir.ui.view,arch_db:sale_order_split_strategy.sale_order_split_strategy_view_tree
msgid "Split Strategies"
msgstr "Strategie suddivisione"
#. module: sale_order_split_strategy
#: model:ir.model.fields,field_description:sale_order_split_strategy.field_sale_order__split_strategy_id
#: model_terms:ir.ui.view,arch_db:sale_order_split_strategy.sale_order_split_strategy_view_form
msgid "Split Strategy"
msgstr "Strategia suddivisione"
#. module: sale_order_split_strategy
#: model_terms:ir.ui.view,arch_db:sale_order_split_strategy.split_to
msgid "This sale order had some of its lines split to"
msgstr "Questo ordine ha alcune righe suddivise in"
#. module: sale_order_split_strategy
#: model_terms:ir.ui.view,arch_db:sale_order_split_strategy.split_from
msgid "This sale order was created after splitting lines from"
msgstr ""
"Questo ordine di vendita è stato creato dopo la suddivisione delle righe da"
#. module: sale_order_split_strategy
#: code:addons/sale_order_split_strategy/models/sale_order.py:0
#, python-format
msgid ""
"This sale order was not split using strategy %(strategy)s because there "
"would not be any lines left on this order."
msgstr ""
"Questo ordine di vendita non è stato suddiviso utilizzando la strategia "
"%(strategy)s perché non c'erano righe nell'ordine."
#. module: sale_order_split_strategy
#: model_terms:ir.ui.view,arch_db:sale_order_split_strategy.split_from
#: model_terms:ir.ui.view,arch_db:sale_order_split_strategy.split_to
msgid "strategy."
msgstr "strategia."
#. module: sale_order_split_strategy
#: model_terms:ir.ui.view,arch_db:sale_order_split_strategy.split_from
#: model_terms:ir.ui.view,arch_db:sale_order_split_strategy.split_to
msgid "using"
msgstr "usando"

View File

@@ -0,0 +1,152 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * sale_order_split_strategy
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
"Language: pt\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
#. module: sale_order_split_strategy
#. odoo-python
#: code:addons/sale_order_split_strategy/models/sale_order.py:0
msgid ""
"Cannot split order %(order_name)s according to its strategy because there "
"are no matching lines"
msgstr ""
#. module: sale_order_split_strategy
#. odoo-python
#: code:addons/sale_order_split_strategy/models/sale_order.py:0
msgid "Cannot split orders %(order_names)s without any split strategy defined"
msgstr ""
#. module: sale_order_split_strategy
#: model:ir.model.fields,field_description:sale_order_split_strategy.field_sale_order_split_strategy__copy_notes
msgid "Copy Notes"
msgstr ""
#. module: sale_order_split_strategy
#: model:ir.model.fields,field_description:sale_order_split_strategy.field_sale_order_split_strategy__copy_sections
msgid "Copy Sections"
msgstr ""
#. module: sale_order_split_strategy
#: model:ir.model.fields,field_description:sale_order_split_strategy.field_sale_order_split_strategy__create_uid
msgid "Created by"
msgstr ""
#. module: sale_order_split_strategy
#: model:ir.model.fields,field_description:sale_order_split_strategy.field_sale_order_split_strategy__create_date
msgid "Created on"
msgstr ""
#. module: sale_order_split_strategy
#: model:ir.model.fields,field_description:sale_order_split_strategy.field_sale_order_split_strategy__display_name
msgid "Display Name"
msgstr ""
#. module: sale_order_split_strategy
#: model:ir.model.fields,field_description:sale_order_split_strategy.field_sale_order_split_strategy__id
msgid "ID"
msgstr ""
#. module: sale_order_split_strategy
#: model:ir.model.fields,field_description:sale_order_split_strategy.field_sale_order_split_strategy__write_uid
msgid "Last Updated by"
msgstr ""
#. module: sale_order_split_strategy
#: model:ir.model.fields,field_description:sale_order_split_strategy.field_sale_order_split_strategy__write_date
msgid "Last Updated on"
msgstr ""
#. module: sale_order_split_strategy
#: model:ir.model.fields,field_description:sale_order_split_strategy.field_sale_order_split_strategy__line_filter_id
msgid "Line Filter"
msgstr ""
#. module: sale_order_split_strategy
#: model:ir.model.fields,field_description:sale_order_split_strategy.field_sale_order_split_strategy__name
msgid "Name"
msgstr ""
#. module: sale_order_split_strategy
#: model:ir.model,name:sale_order_split_strategy.model_sale_order_split_strategy
msgid "Order split strategy"
msgstr ""
#. module: sale_order_split_strategy
#: model:ir.model.fields,field_description:sale_order_split_strategy.field_sale_order_split_strategy__remove_empty_sections_after_split
msgid "Remove Empty Sections After Split"
msgstr ""
#. module: sale_order_split_strategy
#: model:ir.model,name:sale_order_split_strategy.model_sale_order
msgid "Sales Order"
msgstr ""
#. module: sale_order_split_strategy
#: model:ir.model.fields,field_description:sale_order_split_strategy.field_sale_order_split_strategy__sequence
msgid "Sequence"
msgstr ""
#. module: sale_order_split_strategy
#: model_terms:ir.ui.view,arch_db:sale_order_split_strategy.view_order_form
msgid "Split"
msgstr ""
#. module: sale_order_split_strategy
#: model:ir.actions.act_window,name:sale_order_split_strategy.sale_order_split_strategy_action
#: model:ir.ui.menu,name:sale_order_split_strategy.sale_order_split_strategy_menu
#: model_terms:ir.ui.view,arch_db:sale_order_split_strategy.sale_order_split_strategy_view_tree
msgid "Split Strategies"
msgstr ""
#. module: sale_order_split_strategy
#: model:ir.model.fields,field_description:sale_order_split_strategy.field_sale_order__split_strategy_id
#: model_terms:ir.ui.view,arch_db:sale_order_split_strategy.sale_order_split_strategy_view_form
msgid "Split Strategy"
msgstr ""
#. module: sale_order_split_strategy
#: model:ir.model.fields,help:sale_order_split_strategy.field_sale_order__split_strategy_id
msgid "The strategy that will be used to split the sales order"
msgstr ""
#. module: sale_order_split_strategy
#: model_terms:ir.ui.view,arch_db:sale_order_split_strategy.split_to
msgid "This sale order had some of its lines split to"
msgstr ""
#. module: sale_order_split_strategy
#: model_terms:ir.ui.view,arch_db:sale_order_split_strategy.split_from
msgid "This sale order was created after splitting lines from"
msgstr ""
#. module: sale_order_split_strategy
#. odoo-python
#: code:addons/sale_order_split_strategy/models/sale_order.py:0
msgid ""
"This sale order was not split using strategy %(strategy)s because there "
"would not be any lines left on this order."
msgstr ""
#. module: sale_order_split_strategy
#: model_terms:ir.ui.view,arch_db:sale_order_split_strategy.split_from
#: model_terms:ir.ui.view,arch_db:sale_order_split_strategy.split_to
msgid "strategy."
msgstr ""
#. module: sale_order_split_strategy
#: model_terms:ir.ui.view,arch_db:sale_order_split_strategy.split_from
#: model_terms:ir.ui.view,arch_db:sale_order_split_strategy.split_to
msgid "using"
msgstr ""

View File

@@ -0,0 +1,151 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * sale_order_split_strategy
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: sale_order_split_strategy
#. odoo-python
#: code:addons/sale_order_split_strategy/models/sale_order.py:0
msgid ""
"Cannot split order %(order_name)s according to its strategy because there "
"are no matching lines"
msgstr ""
#. module: sale_order_split_strategy
#. odoo-python
#: code:addons/sale_order_split_strategy/models/sale_order.py:0
msgid "Cannot split orders %(order_names)s without any split strategy defined"
msgstr ""
#. module: sale_order_split_strategy
#: model:ir.model.fields,field_description:sale_order_split_strategy.field_sale_order_split_strategy__copy_notes
msgid "Copy Notes"
msgstr ""
#. module: sale_order_split_strategy
#: model:ir.model.fields,field_description:sale_order_split_strategy.field_sale_order_split_strategy__copy_sections
msgid "Copy Sections"
msgstr ""
#. module: sale_order_split_strategy
#: model:ir.model.fields,field_description:sale_order_split_strategy.field_sale_order_split_strategy__create_uid
msgid "Created by"
msgstr ""
#. module: sale_order_split_strategy
#: model:ir.model.fields,field_description:sale_order_split_strategy.field_sale_order_split_strategy__create_date
msgid "Created on"
msgstr ""
#. module: sale_order_split_strategy
#: model:ir.model.fields,field_description:sale_order_split_strategy.field_sale_order_split_strategy__display_name
msgid "Display Name"
msgstr ""
#. module: sale_order_split_strategy
#: model:ir.model.fields,field_description:sale_order_split_strategy.field_sale_order_split_strategy__id
msgid "ID"
msgstr ""
#. module: sale_order_split_strategy
#: model:ir.model.fields,field_description:sale_order_split_strategy.field_sale_order_split_strategy__write_uid
msgid "Last Updated by"
msgstr ""
#. module: sale_order_split_strategy
#: model:ir.model.fields,field_description:sale_order_split_strategy.field_sale_order_split_strategy__write_date
msgid "Last Updated on"
msgstr ""
#. module: sale_order_split_strategy
#: model:ir.model.fields,field_description:sale_order_split_strategy.field_sale_order_split_strategy__line_filter_id
msgid "Line Filter"
msgstr ""
#. module: sale_order_split_strategy
#: model:ir.model.fields,field_description:sale_order_split_strategy.field_sale_order_split_strategy__name
msgid "Name"
msgstr ""
#. module: sale_order_split_strategy
#: model:ir.model,name:sale_order_split_strategy.model_sale_order_split_strategy
msgid "Order split strategy"
msgstr ""
#. module: sale_order_split_strategy
#: model:ir.model.fields,field_description:sale_order_split_strategy.field_sale_order_split_strategy__remove_empty_sections_after_split
msgid "Remove Empty Sections After Split"
msgstr ""
#. module: sale_order_split_strategy
#: model:ir.model,name:sale_order_split_strategy.model_sale_order
msgid "Sales Order"
msgstr ""
#. module: sale_order_split_strategy
#: model:ir.model.fields,field_description:sale_order_split_strategy.field_sale_order_split_strategy__sequence
msgid "Sequence"
msgstr ""
#. module: sale_order_split_strategy
#: model_terms:ir.ui.view,arch_db:sale_order_split_strategy.view_order_form
msgid "Split"
msgstr ""
#. module: sale_order_split_strategy
#: model:ir.actions.act_window,name:sale_order_split_strategy.sale_order_split_strategy_action
#: model:ir.ui.menu,name:sale_order_split_strategy.sale_order_split_strategy_menu
#: model_terms:ir.ui.view,arch_db:sale_order_split_strategy.sale_order_split_strategy_view_tree
msgid "Split Strategies"
msgstr ""
#. module: sale_order_split_strategy
#: model:ir.model.fields,field_description:sale_order_split_strategy.field_sale_order__split_strategy_id
#: model_terms:ir.ui.view,arch_db:sale_order_split_strategy.sale_order_split_strategy_view_form
msgid "Split Strategy"
msgstr ""
#. module: sale_order_split_strategy
#: model:ir.model.fields,help:sale_order_split_strategy.field_sale_order__split_strategy_id
msgid "The strategy that will be used to split the sales order"
msgstr ""
#. module: sale_order_split_strategy
#: model_terms:ir.ui.view,arch_db:sale_order_split_strategy.split_to
msgid "This sale order had some of its lines split to"
msgstr ""
#. module: sale_order_split_strategy
#: model_terms:ir.ui.view,arch_db:sale_order_split_strategy.split_from
msgid "This sale order was created after splitting lines from"
msgstr ""
#. module: sale_order_split_strategy
#. odoo-python
#: code:addons/sale_order_split_strategy/models/sale_order.py:0
msgid ""
"This sale order was not split using strategy %(strategy)s because there "
"would not be any lines left on this order."
msgstr ""
#. module: sale_order_split_strategy
#: model_terms:ir.ui.view,arch_db:sale_order_split_strategy.split_from
#: model_terms:ir.ui.view,arch_db:sale_order_split_strategy.split_to
msgid "strategy."
msgstr ""
#. module: sale_order_split_strategy
#: model_terms:ir.ui.view,arch_db:sale_order_split_strategy.split_from
#: model_terms:ir.ui.view,arch_db:sale_order_split_strategy.split_to
msgid "using"
msgstr ""

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

View File

@@ -0,0 +1,3 @@
[build-system]
requires = ["whool"]
build-backend = "whool.buildapi"

View File

@@ -0,0 +1 @@
* Akim Juillerat <akim.juillerat@camptocamp.com>

View File

@@ -0,0 +1,6 @@
This module provides the possibility to define split strategies to be
applied on sales order for splitting.
This is useful (but not only) when a sales order from external sources (webshop, edi) includes
products that need to be shipped by different standards (temperature/environment)
and so with different carriers.

View File

@@ -0,0 +1,8 @@
To use this module you first need to create splitting strategies to apply on sales order.
Sales -> Configuration -> Split Startegies
The splitting is based on an ir.filter applied on sale.order.line.
Then on sales order, the Split Strategy field allows to select the corresponding strategy.
After which a button `Split` is available on the form to split an order.

View File

@@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_sale_order_split_strategy_user,access_sale_order_split_strategy_user,model_sale_order_split_strategy,sales_team.group_sale_salesman,1,0,0,0
access_sale_order_split_strategy_manager,access_sale_order_split_strategy_manager,model_sale_order_split_strategy,sales_team.group_sale_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_sale_order_split_strategy_user access_sale_order_split_strategy_user model_sale_order_split_strategy sales_team.group_sale_salesman 1 0 0 0
3 access_sale_order_split_strategy_manager access_sale_order_split_strategy_manager model_sale_order_split_strategy sales_team.group_sale_manager 1 1 1 1

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@@ -0,0 +1,441 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
<title>Sale Order Split Strategy</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
Despite the name, some widely supported CSS2 features are used.
See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: gray; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic, pre.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="sale-order-split-strategy">
<h1 class="title">Sale Order Split Strategy</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:6b3dde9afa8ab7731de990ae24b9dc07d8d2fa407d4ffa31030a6bdc676312cd
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Alpha" src="https://img.shields.io/badge/maturity-Alpha-red.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/sale-workflow/tree/18.0/sale_order_split_strategy"><img alt="OCA/sale-workflow" src="https://img.shields.io/badge/github-OCA%2Fsale--workflow-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/sale-workflow-18-0/sale-workflow-18-0-sale_order_split_strategy"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/sale-workflow&amp;target_branch=18.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>This module provides the possibility to define split strategies to be
applied on sales order for splitting.</p>
<p>This is useful (but not only) when a sales order from external sources
(webshop, edi) includes products that need to be shipped by different
standards (temperature/environment) and so with different carriers.</p>
<div class="admonition important">
<p class="first admonition-title">Important</p>
<p class="last">This is an alpha version, the data model and design can change at any time without warning.
Only for development or testing purpose, do not use in production.
<a class="reference external" href="https://odoo-community.org/page/development-status">More details on development status</a></p>
</div>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#usage" id="toc-entry-1">Usage</a></li>
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-2">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-3">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-4">Authors</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-5">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#toc-entry-1">Usage</a></h1>
<p>To use this module you first need to create splitting strategies to
apply on sales order.</p>
<pre class="literal-block">
Sales -&gt; Configuration -&gt; Split Startegies
</pre>
<p>The splitting is based on an ir.filter applied on sale.order.line.</p>
<p>Then on sales order, the Split Strategy field allows to select the
corresponding strategy. After which a button <tt class="docutils literal">Split</tt> is available on
the form to split an order.</p>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#toc-entry-2">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/sale-workflow/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/sale-workflow/issues/new?body=module:%20sale_order_split_strategy%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#toc-entry-3">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#toc-entry-4">Authors</a></h2>
<ul class="simple">
<li>Camptocamp</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#toc-entry-5">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org">
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
</a>
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.</p>
<p>Current <a class="reference external" href="https://odoo-community.org/page/maintainer-role">maintainer</a>:</p>
<p><a class="reference external image-reference" href="https://github.com/grindtildeath"><img alt="grindtildeath" src="https://github.com/grindtildeath.png?size=40px" /></a></p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/sale-workflow/tree/18.0/sale_order_split_strategy">OCA/sale-workflow</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1 @@
from . import test_split_strategy

View File

@@ -0,0 +1,168 @@
# Copyright 2024 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
from odoo.tests import Form
from odoo.addons.base.tests.common import BaseCommon
class TestSplitStrategy(BaseCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True))
cls.product_consu_1 = cls.env["product.product"].search(
[("type", "!=", "service")], limit=1
)
cls.product_consu_2 = cls.env["product.product"].search(
[("type", "!=", "service")], offset=1, limit=1
)
cls.product_service_1 = cls.env["product.product"].search(
[("type", "=", "service")], limit=1
)
cls.product_service_2 = cls.env["product.product"].search(
[("type", "=", "service")], offset=1, limit=1
)
cls.product_type_not_service_filter = cls.env["ir.filters"].create(
{
"name": "Product type not service",
"domain": "[('product_id.type', '!=', 'service')]",
"model_id": "sale.order.line",
}
)
cls.product_type_not_service_strategy = cls.env[
"sale.order.split.strategy"
].create(
{
"name": "Product type",
"line_filter_id": cls.product_type_not_service_filter.id,
}
)
@classmethod
def _create_order(cls):
order_form = Form(cls.env["sale.order"])
order_form.partner_id = cls.env["res.partner"].search([], limit=1)
for product in [
cls.product_consu_1,
cls.product_consu_2,
cls.product_service_1,
cls.product_service_2,
]:
with order_form.order_line.new() as line_form:
line_form.product_id = product
line_form.product_uom_qty = 1
return order_form.save()
def _set_sequences_and_sections(self, order):
for seq, line in enumerate(order.order_line, 1):
line.sequence = seq * 10
# First section includes only consumable
first_section = order.order_line.create(
{
"order_id": order.id,
"display_type": "line_section",
"name": "First line",
"sequence": 5,
}
)
# Middle section includes both consumable + service
middle_section = order.order_line.create(
{
"order_id": order.id,
"display_type": "line_section",
"name": "Middle lines",
"sequence": 15,
}
)
# Last section includes only service
last_section = order.order_line.create(
{
"order_id": order.id,
"display_type": "line_section",
"name": "Last line",
"sequence": 35,
}
)
order_lines = order.order_line.sorted()
self.assertEqual(order_lines[0], first_section)
self.assertEqual(order_lines[2], middle_section)
self.assertEqual(order_lines[5], last_section)
def test_split_product_type(self):
order = self._create_order()
order.split_strategy_id = self.product_type_not_service_strategy
self.assertEqual(len(order.order_line), 4)
new_order = order.action_split()
self.assertEqual(len(order.order_line), 2)
self.assertNotIn(self.product_consu_1, order.order_line.mapped("product_id"))
self.assertNotIn(self.product_consu_2, order.order_line.mapped("product_id"))
self.assertIn(self.product_service_1, order.order_line.mapped("product_id"))
self.assertIn(self.product_service_2, order.order_line.mapped("product_id"))
self.assertEqual(len(new_order.order_line), 2)
self.assertIn(self.product_consu_1, new_order.order_line.mapped("product_id"))
self.assertIn(self.product_consu_2, new_order.order_line.mapped("product_id"))
self.assertNotIn(
self.product_service_1, new_order.order_line.mapped("product_id")
)
self.assertNotIn(
self.product_service_2, new_order.order_line.mapped("product_id")
)
def test_split_product_type_copy_sections(self):
order = self._create_order()
self._set_sequences_and_sections(order)
order.order_line.sorted()
self.product_type_not_service_strategy.copy_sections = True
order.split_strategy_id = self.product_type_not_service_strategy
new_order = order.action_split()
# As new order includes only consu product, it must include
# first + middle sections
self.assertEqual(len(new_order.order_line), 4)
self.assertIn("First line", new_order.order_line.mapped("name"))
self.assertIn("Middle lines", new_order.order_line.mapped("name"))
self.assertNotIn("Last line", new_order.order_line.mapped("name"))
# As new order includes only service product, it must include
# middle + last sections
self.assertEqual(len(order.order_line), 4)
self.assertNotIn("First line", order.order_line.mapped("name"))
self.assertIn("Middle lines", order.order_line.mapped("name"))
self.assertIn("Last line", order.order_line.mapped("name"))
def test_split_product_type_not_copy_sections(self):
order = self._create_order()
self._set_sequences_and_sections(order)
self.assertFalse(self.product_type_not_service_strategy.copy_sections)
order.split_strategy_id = self.product_type_not_service_strategy
new_order = order.action_split()
# New order does not have any section
self.assertEqual(len(new_order.order_line), 2)
self.assertNotIn("line_section", new_order.order_line.mapped("display_type"))
# Old order keeps all the sections
self.assertEqual(len(order.order_line), 5)
self.assertIn("First line", order.order_line.mapped("name"))
self.assertIn("Middle lines", order.order_line.mapped("name"))
self.assertIn("Last line", order.order_line.mapped("name"))
def test_split_product_type_not_copy_sections_remove_empty(self):
order = self._create_order()
self._set_sequences_and_sections(order)
# Add extra empty section at the end
order.order_line.create(
{
"order_id": order.id,
"display_type": "line_section",
"name": "Empty section",
"sequence": 999,
}
)
self.assertFalse(self.product_type_not_service_strategy.copy_sections)
self.product_type_not_service_strategy.remove_empty_sections_after_split = True
order.split_strategy_id = self.product_type_not_service_strategy
order.action_split()
# Old order has empty sections removed
self.assertEqual(len(order.order_line), 4)
self.assertNotIn("First line", order.order_line.mapped("name"))
self.assertIn("Middle lines", order.order_line.mapped("name"))
self.assertIn("Last line", order.order_line.mapped("name"))
self.assertNotIn("Empty section", order.order_line.mapped("name"))

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<template id="split_from">
<t>
<div>
This sale order was created after splitting lines from <a
href="#"
data-oe-model="sale.order"
t-att-data-oe-id="from_order.id"
>
<t t-esc="from_order.display_name" />
</a> using <t t-esc="from_order.split_strategy_id.name" /> strategy.
</div>
</t>
</template>
<template id="split_to">
<t>
<div>
This sale order had some of its lines split to <a
href="#"
data-oe-model="sale.order"
t-att-data-oe-id="to_order.id"
>
<t t-esc="to_order.display_name" />
</a> using <t t-esc="from_order.split_strategy_id.name" /> strategy.
</div>
</t>
</template>
</odoo>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="view_order_form" model="ir.ui.view">
<field name="name">sale.order.form</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form" />
<field name="arch" type="xml">
<button name="action_draft" position="after">
<button
name="action_split"
invisible="state != 'draft' or split_strategy_id == False"
type="object"
string="Split"
/>
</button>
<group name="sales_person" position="inside">
<field name="split_strategy_id" />
</group>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="sale_order_split_strategy_view_form" model="ir.ui.view">
<field name="name">sale.order.split.strategy.view.form</field>
<field name="model">sale.order.split.strategy</field>
<field name="arch" type="xml">
<form string="Split Strategy">
<sheet>
<div class="oe_title">
<div class="oe_edit_only">
<label for="name" />
</div>
<h1>
<field name="name" />
</h1>
</div>
<group>
<group>
<field name="line_filter_id" />
<field name="copy_sections" />
<field
name="remove_empty_sections_after_split"
invisible="copy_sections == False"
/>
<field name="copy_notes" />
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="sale_order_split_strategy_view_tree" model="ir.ui.view">
<field name="name">sale.order.split.strategy.view.tree</field>
<field name="model">sale.order.split.strategy</field>
<field name="arch" type="xml">
<list string="Split Strategies">
<field name="sequence" widget="handle" />
<field name="name" />
</list>
</field>
</record>
<record id="sale_order_split_strategy_action" model="ir.actions.act_window">
<field name="name">Split Strategies</field>
<field name="res_model">sale.order.split.strategy</field>
<field name="view_mode">list,form</field>
</record>
<menuitem
name="Split Strategies"
id="sale_order_split_strategy_menu"
parent="sale.menu_sales_config"
action="sale_order_split_strategy_action"
sequence="30"
/>
</odoo>