Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions stock_available_to_promise_release_carrier_alternative/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
========================================================
Stock Available to Promise Release - Carrier Alternative
========================================================

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:0360a786d5b6302d09d1f60fa6f6ab6aee9b16dcc0148712292e165492828868
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |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%2Fstock--logistics--reservation-lightgray.png?logo=github
:target: https://github.com/OCA/stock-logistics-reservation/tree/18.0/stock_available_to_promise_release_carrier_alternative
:alt: OCA/stock-logistics-reservation
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/stock-logistics-reservation-18-0/stock-logistics-reservation-18-0-stock_available_to_promise_release_carrier_alternative
: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/stock-logistics-reservation&target_branch=18.0
:alt: Try me on Runboat

|badge1| |badge2| |badge3| |badge4| |badge5|

This module allows to define preferred shipping methods in order to fine
tune the selection of proper delivery carrier from delivery operations
according to the quantity available to promise.

Moreover it provides the possibility to force the recomputation of
preferred delivery carrier on the release of operations for Delivery
Orders.

**Table of contents**

.. contents::
:local:

Configuration
=============

Go to any delivery method in order to define its alternative delivery
methods.

At release, a new carrier might be selected among alternative carriers.
This has to comply with a few rules, such as carrier max weight and
volume. You also can add extra rules by editing a delivery method's
Picking Domain.

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OCA/stock-logistics-reservation/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/stock-logistics-reservation/issues/new?body=module:%20stock_available_to_promise_release_carrier_alternative%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
* BCIM

Contributors
------------

- Jacques-Etienne Baudoux <je@bcim.be>
- Matthieu Méquignon <matthieu.mequignon@camptocamp.com>

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.

This module is part of the `OCA/stock-logistics-reservation <https://github.com/OCA/stock-logistics-reservation/tree/18.0/stock_available_to_promise_release_carrier_alternative>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Copyright 2020 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
{
"name": "Stock Available to Promise Release - Carrier Alternative",
"summary": "Advanced selection of preferred shipping methods",
"version": "18.0.1.0.0",
"category": "Operations/Inventory/Delivery",
"website": "https://github.com/OCA/stock-logistics-reservation",
"author": "Camptocamp, BCIM, Odoo Community Association (OCA)",
"license": "AGPL-3",
"application": False,
"installable": True,
"depends": [
"delivery_carrier_picking_valid",
"stock_available_to_promise_release",
"delivery_procurement_group_carrier",
],
"data": [
"views/delivery_carrier.xml",
],
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from . import procurement_group
from . import stock_move
from . import stock_picking
from . import delivery_carrier
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Copyright 2025 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)

from odoo import fields, models

ALTERNATIVE_CARRIER_IDS_HELP = """
Change delivery to one of those alternative carriers if the conditions are met.
Evaluated carriers are this carrier and those alternatives sorted by sequence.
The first valid carrier will be selected
"""


class DeliveryCarrier(models.Model):
_inherit = "delivery.carrier"

alternative_carrier_ids = fields.Many2many(
comodel_name="delivery.carrier",
relation="carrier_alternative_carrier_rel",
column1="carrier_id",
column2="alternative_id",
help=ALTERNATIVE_CARRIER_IDS_HELP,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Copyright 2020 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
from odoo import fields, models


class ProcurementGroup(models.Model):
_inherit = "procurement.group"

picking_ids = fields.One2many(
comodel_name="stock.picking", inverse_name="group_id", readonly=True
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Copyright 2020 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)

from odoo import models
from odoo.tools import groupby


class StockMove(models.Model):
_inherit = "stock.move"

def _get_processible_quantity(self):
self.ensure_one()
# use available promised qty to estimate the shipping weight
return self.ordered_available_to_promise_uom_qty

def _get_new_picking_values(self):
vals = super()._get_new_picking_values()
# Take the carrier_id from the group only when we have a related line
# (i.e. we are in an OUT). It reflects the code of the super method in
# "delivery" which takes the carrier of the related SO through SO line
if self.sale_line_id:
if group_carrier := self.group_id.carrier_id:
vals["carrier_id"] = group_carrier.id
return vals

def _before_release(self):
# Apply alternative carrier before updating the date
self = self._apply_alternative_carrier()
return super()._before_release()

def _apply_alternative_carrier(self):
for picking, moves_list in groupby(self, key=lambda move: move.picking_id):
if not picking.carrier_id.alternative_carrier_ids:
continue
# Don't apply alternative carrier when there is already a released move
if any(
not move.need_release and move.state != "cancel"
for move in picking.move_ids
):
continue
# For computing the best carrier, we need the released moves
# to be isolated in a dedicated picking.
# This is for instance required to have the picking estimated
# shipping weight only for the released moves
moves = self.browse().union(*moves_list)
with self.env.cr.savepoint() as savepoint:
all_need_release_moves = picking.move_ids.filtered("need_release")
if moves != all_need_release_moves:
moves._unreleased_to_backorder(split_order=True)
picking = moves.picking_id
# If a better carrier is found, assign it, otherwise rollback
carrier_changed = picking._apply_alternative_carrier()
if not carrier_changed:
savepoint.rollback()
return self
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Copyright 2025 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
from odoo import models


class StockPicking(models.Model):
_inherit = "stock.picking"

def _apply_alternative_carrier(self):
self.ensure_one()
carrier = self._get_preferred_carrier()
if not carrier:
return False
if carrier == self.carrier_id:
return False
# Set a context key to not trigger a carrier change on the
# procurement group
self.with_context(skip_align_group_carrier=True).carrier_id = carrier
defaults = {
"carrier_id": carrier.id,
"name": self.env._(
"%s - Alternative carrier %s", self.group_id.name, carrier.name
),
}
self.group_id = self.group_id.copy(default=defaults)
active_moves = self.move_ids.filtered(
lambda m: m.state not in ("done", "cancel")
)
active_moves.group_id = self.group_id
# Manage the carrier route, but skip when the route was forced
if (
(rerouted_moves := active_moves.filtered(lambda m: not m.route_ids))
and (routes := carrier.route_ids.filtered("active"))
and (
rule := self.env["procurement.group"]._get_rule(
self.env["product.product"],
self.location_dest_id,
{"route_ids": routes},
)
)
):
rerouted_moves.write(
{
"location_id": rule.location_src_id.id,
"rule_id": rule.id,
"picking_type_id": rule.picking_type_id.id,
"procure_method": rule.procure_method,
"propagate_cancel": rule.propagate_cancel,
}
)
rerouted_moves._unreleased_to_backorder(split_order=True)
# If all moves are moved to another picking, the initial picking will
# be draft. Force it to cancel.
if not self.move_ids:
self.state = "cancel"
return True

def _get_preferred_carrier(self):
self.ensure_one()
picking_carrier = self.carrier_id
# we consider current carrier as well allowing to configure better
# carriers as alternative or less restrictive carrier as fallback
possible_carriers = picking_carrier | picking_carrier.alternative_carrier_ids
for carrier in possible_carriers.sorted("sequence"):
if carrier._match_picking(self):
return carrier
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[build-system]
requires = ["whool"]
build-backend = "whool.buildapi"
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Go to any delivery method in order to define its alternative delivery methods.

At release, a new carrier might be selected among alternative carriers.
This has to comply with a few rules, such as carrier max weight and volume.
You also can add extra rules by editing a delivery method's Picking Domain.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- Jacques-Etienne Baudoux \<je@bcim.be\>
- Matthieu Méquignon \<matthieu.mequignon@camptocamp.com\>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
This module allows to define preferred shipping methods in order to fine
tune the selection of proper delivery carrier from delivery operations
according to the quantity available to promise.

Moreover it provides the possibility to force the recomputation of
preferred delivery carrier on the release of operations for Delivery
Orders.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading