-
-
Notifications
You must be signed in to change notification settings - Fork 698
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
29 changed files
with
954 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from . import models |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# Copyright 2019 Camptocamp (https://www.camptocamp.com) | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). | ||
|
||
{ | ||
"name": "Stock Available to Promise Release - Sale Integration", | ||
"version": "13.0.1.0.0", | ||
"summary": "Integration between Sales and Available to Promise Release", | ||
"author": "Camptocamp,Odoo Community Association (OCA)", | ||
"category": "Stock Management", | ||
"depends": ["sale_stock", "stock_available_to_promise_release"], | ||
"data": [], | ||
"installable": True, | ||
"license": "AGPL-3", | ||
"application": False, | ||
"development_status": "Alpha", | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from . import sale_order_line |
13 changes: 13 additions & 0 deletions
13
sale_stock_available_to_promise_release/models/sale_order_line.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# Copyright 2019 Camptocamp (https://www.camptocamp.com) | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). | ||
|
||
from odoo import models | ||
|
||
|
||
class SaleOrderLine(models.Model): | ||
_inherit = "sale.order.line" | ||
|
||
def _prepare_procurement_values(self, group_id=False): | ||
values = super()._prepare_procurement_values(group_id) | ||
values["date_priority"] = self.order_id.date_order | ||
return values |
1 change: 1 addition & 0 deletions
1
sale_stock_available_to_promise_release/readme/CONTRIBUTORS.rst
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
* Guewen Baconnier <guewen.baconnier@camptocamp.com> |
2 changes: 2 additions & 0 deletions
2
sale_stock_available_to_promise_release/readme/DESCRIPTION.rst
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
Integrate the Release of Operation based on Available to Promise with Sales. The Priority Date of Stock | ||
Moves will be equal to the confirmation date of their sales order. |
1 change: 1 addition & 0 deletions
1
...le_stock_available_to_promise_release/odoo/addons/sale_stock_available_to_promise_release
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
../../../../sale_stock_available_to_promise_release |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import setuptools | ||
|
||
setuptools.setup( | ||
setup_requires=['setuptools-odoo'], | ||
odoo_addon=True, | ||
) |
1 change: 1 addition & 0 deletions
1
setup/stock_available_to_promise_release/odoo/addons/stock_available_to_promise_release
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
../../../../stock_available_to_promise_release |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import setuptools | ||
|
||
setuptools.setup( | ||
setup_requires=['setuptools-odoo'], | ||
odoo_addon=True, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
from . import models | ||
from . import wizards |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# Copyright 2019 Camptocamp (https://www.camptocamp.com) | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). | ||
|
||
{ | ||
"name": "Stock Available to Promise Release", | ||
"version": "13.0.1.0.0", | ||
"summary": "Release Operations based on available to promise", | ||
"author": "Camptocamp,Odoo Community Association (OCA)", | ||
"category": "Stock Management", | ||
"depends": ["stock"], | ||
"data": [ | ||
"views/stock_move_views.xml", | ||
"views/stock_picking_views.xml", | ||
"views/stock_location_route_views.xml", | ||
"wizards/stock_move_release_views.xml", | ||
], | ||
"installable": True, | ||
"license": "AGPL-3", | ||
"application": False, | ||
"development_status": "Alpha", | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
from . import stock_move | ||
from . import stock_location_route | ||
from . import stock_picking | ||
from . import stock_rule |
16 changes: 16 additions & 0 deletions
16
stock_available_to_promise_release/models/stock_location_route.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# Copyright 2019 Camptocamp (https://www.camptocamp.com) | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). | ||
|
||
from odoo import fields, models | ||
|
||
|
||
class Route(models.Model): | ||
_inherit = "stock.location.route" | ||
|
||
available_to_promise_defer_pull = fields.Boolean( | ||
string="Release based on Available to Promise", | ||
default=False, | ||
help="Do not create chained moved automatically for delivery. " | ||
"Transfers must be released manually when they have enough available" | ||
" to promise.", | ||
) |
147 changes: 147 additions & 0 deletions
147
stock_available_to_promise_release/models/stock_move.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
# Copyright 2019 Camptocamp (https://www.camptocamp.com) | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). | ||
|
||
from odoo import api, fields, models | ||
from odoo.osv import expression | ||
from odoo.tools import float_compare | ||
|
||
from odoo.addons import decimal_precision as dp | ||
|
||
|
||
class StockMove(models.Model): | ||
_inherit = "stock.move" | ||
|
||
date_priority = fields.Datetime( | ||
string="Priority Date", | ||
index=True, | ||
default=fields.Datetime.now, | ||
help="Date/time used to sort moves to deliver first. " | ||
"Used to calculate the ordered available to promise.", | ||
) | ||
ordered_available_to_promise = fields.Float( | ||
"Ordered Available to Promise", | ||
compute="_compute_ordered_available_to_promise", | ||
digits=dp.get_precision("Product Unit of Measure"), | ||
help="Available to Promise quantity minus quantities promised " | ||
" to older promised operations.", | ||
) | ||
need_release = fields.Boolean() | ||
|
||
@api.depends() | ||
def _compute_ordered_available_to_promise(self): | ||
for move in self: | ||
move.ordered_available_to_promise = move._ordered_available_to_promise() | ||
|
||
def _should_compute_ordered_available_to_promise(self): | ||
return ( | ||
self.picking_code == "outgoing" | ||
and self.need_release | ||
and not self.product_id.type == "consu" | ||
and not self.location_id.should_bypass_reservation() | ||
) | ||
|
||
def _action_cancel(self): | ||
super()._action_cancel() | ||
self.write({"need_release": False}) | ||
return True | ||
|
||
def _ordered_available_to_promise(self): | ||
if not self._should_compute_ordered_available_to_promise(): | ||
return 0.0 | ||
available = self.product_id.with_context( | ||
location=self.warehouse_id.lot_stock_id.id | ||
).virtual_available | ||
return max( | ||
min(available - self._previous_promised_qty(), self.product_qty), 0.0 | ||
) | ||
|
||
def _previous_promised_quantity_domain(self): | ||
domain = [ | ||
("need_release", "=", True), | ||
("product_id", "=", self.product_id.id), | ||
("date_priority", "<=", self.date_priority), | ||
("warehouse_id", "=", self.warehouse_id.id), | ||
] | ||
return domain | ||
|
||
def _previous_promised_qty(self): | ||
previous_moves = self.search( | ||
expression.AND( | ||
[self._previous_promised_quantity_domain(), [("id", "!=", self.id)]] | ||
) | ||
) | ||
promised_qty = sum( | ||
previous_moves.mapped( | ||
lambda move: max(move.product_qty - move.reserved_availability, 0.0) | ||
) | ||
) | ||
return promised_qty | ||
|
||
def release_available_to_promise(self): | ||
self._run_stock_rule() | ||
|
||
def _prepare_move_split_vals(self, qty): | ||
vals = super()._prepare_move_split_vals(qty) | ||
# The method set procure_method as 'make_to_stock' by default on split, | ||
# but we want to keep 'make_to_order' for chained moves when we split | ||
# a partially available move in _run_stock_rule(). | ||
if self.env.context.get("release_available_to_promise"): | ||
vals.update({"procure_method": self.procure_method, "need_release": True}) | ||
return vals | ||
|
||
def _run_stock_rule(self): | ||
"""Launch procurement group run method with remaining quantity | ||
As we only generate chained moves for the quantity available minus the | ||
quantity promised to older moves, to delay the reservation at the | ||
latest, we have to periodically retry to assign the remaining | ||
quantities. | ||
""" | ||
precision = self.env["decimal.precision"].precision_get( | ||
"Product Unit of Measure" | ||
) | ||
procurement_requests = [] | ||
pulled_moves = self.env["stock.move"] | ||
for move in self: | ||
if not move.need_release: | ||
continue | ||
if move.state not in ("confirmed", "waiting"): | ||
continue | ||
# do not use the computed field, because it will keep | ||
# a value in cache that we cannot invalidate declaratively | ||
available_quantity = move._ordered_available_to_promise() | ||
if float_compare(available_quantity, 0, precision_digits=precision) <= 0: | ||
continue | ||
|
||
quantity = min(move.product_qty, available_quantity) | ||
remaining = move.product_qty - quantity | ||
|
||
if float_compare(remaining, 0, precision_digits=precision) > 0: | ||
if move.picking_id.move_type == "one": | ||
# we don't want to deliver unless we can deliver all at | ||
# once | ||
continue | ||
move.with_context(release_available_to_promise=True)._split(remaining) | ||
|
||
values = move._prepare_procurement_values() | ||
procurement_requests.append( | ||
self.env["procurement.group"].Procurement( | ||
move.product_id, | ||
move.product_uom_qty, | ||
move.product_uom, | ||
move.location_id, | ||
move.rule_id and move.rule_id.name or "/", | ||
move.origin, | ||
move.company_id, | ||
values, | ||
) | ||
) | ||
pulled_moves |= move | ||
|
||
self.env["procurement.group"].run_defer(procurement_requests) | ||
|
||
while pulled_moves: | ||
pulled_moves._action_assign() | ||
pulled_moves = pulled_moves.mapped("move_orig_ids") | ||
|
||
return True |
21 changes: 21 additions & 0 deletions
21
stock_available_to_promise_release/models/stock_picking.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# Copyright 2019 Camptocamp (https://www.camptocamp.com) | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). | ||
|
||
from odoo import api, fields, models | ||
|
||
|
||
class StockPicking(models.Model): | ||
_inherit = "stock.picking" | ||
|
||
# Add store on the field, as it is quite used in the searches, | ||
# and this is an easy-win to reduce the number of SQL queries. | ||
picking_type_code = fields.Selection(store=True) | ||
need_release = fields.Boolean(compute="_compute_need_release") | ||
|
||
@api.depends("move_lines.need_release") | ||
def _compute_need_release(self): | ||
for picking in self: | ||
picking.need_release = any(move.need_release for move in picking.move_lines) | ||
|
||
def release_available_to_promise(self): | ||
self.mapped("move_lines").release_available_to_promise() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
# Copyright 2019 Camptocamp (https://www.camptocamp.com) | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). | ||
import logging | ||
|
||
from odoo import fields, models | ||
|
||
_logger = logging.getLogger(__name__) | ||
|
||
|
||
class StockRule(models.Model): | ||
_inherit = "stock.rule" | ||
|
||
def _run_pull(self, procurements): | ||
actions_to_run = [] | ||
|
||
for procurement, rule in procurements: | ||
if ( | ||
not self.env.context.get("_rule_no_available_defer") | ||
and rule.route_id.available_to_promise_defer_pull | ||
# We still want to create the first part of the chain | ||
and not rule.picking_type_id.code == "outgoing" | ||
): | ||
moves = procurement.values.get("move_dest_ids") | ||
# Track the moves that needs to have their pull rule | ||
# done. Before the 'pull' is done, we don't know the | ||
# which route is chosen. We update the destination | ||
# move (ie. the outgoing) when the current route | ||
# defers the pull rules and return so we don't create | ||
# the next move of the chain (pick or pack). | ||
if moves: | ||
moves.write({"need_release": True}) | ||
else: | ||
actions_to_run.append((procurement, rule)) | ||
|
||
super()._run_pull(actions_to_run) | ||
# use first of list of ids and browse it for performance | ||
move_ids = [ | ||
move.id | ||
for move in procurement.values.get("move_dest_ids", []) | ||
for procurement, _rule in actions_to_run | ||
] | ||
if move_ids: | ||
moves = self.env["stock.move"].browse(move_ids) | ||
moves.filtered(lambda r: r.need_release).write({"need_release": False}) | ||
return True | ||
|
||
|
||
class ProcurementGroup(models.Model): | ||
_inherit = "procurement.group" | ||
|
||
def run_defer(self, procurements): | ||
actions_to_run = [] | ||
for procurement in procurements: | ||
values = procurement.values | ||
values.setdefault("company_id", self.env.company) | ||
values.setdefault("priority", "1") | ||
values.setdefault("date_planned", fields.Datetime.now()) | ||
rule = self._get_rule( | ||
procurement.product_id, procurement.location_id, procurement.values | ||
) | ||
if rule.action in ("pull", "pull_push"): | ||
actions_to_run.append((procurement, rule)) | ||
|
||
if actions_to_run: | ||
rule.with_context(_rule_no_available_defer=True)._run_pull(actions_to_run) | ||
return True |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
In Inventory > Configuration > Warehouses, activate the option "Release based on Available to Promise" | ||
when you want to use the feature. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
* Guewen Baconnier <guewen.baconnier@camptocamp.com> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
Currently the reservation is performed by adding reserved quantities on quants, | ||
which is fine as long as the reservation is made right after the order | ||
confirmation. This way, the first arrived, first served principle is always | ||
applied. But if you release warehouse operations in a chosen order (through | ||
deliver round for example), then you need to be sure the reservations are made | ||
in respect to the first arrived first served principle and not driven by the | ||
order you choose to release your operations. | ||
|
||
Allow each delivery move to mark a quantity as virtually reserved. Simple rule | ||
would be first ordered, first served. More complex rules could be implemented. | ||
|
||
When the reservation of a picking move occurs, the quantity that is reserved is | ||
then based on the quantity that was promised to the customer (available to promise): | ||
|
||
* The moves can be reserved in any order, the right quantity is always reserved | ||
* The removal strategy is computed only when the reservation occurs. If you | ||
reserve order 2 before order 1 (because you have/want to deliver order 2) you | ||
can apply correctly fifo/fefo. | ||
|
||
* For instance order 1 must be delivered in 1 month, order 2 must be delivered now. | ||
* Virtually lock quantities to be able to serve order 1 | ||
* Reserve remaining quantity for order 2 and apply fefo | ||
|
||
* Allow to limit the promised quantity in time. If a customer orders now for a | ||
planned delivery in 2 months, then allow to not lock this quantity as | ||
virtually reserved | ||
* Allow to perform reservations jointly with your delivery rounds planning. | ||
Reserve only the quants you planned to deliver. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
When an outgoing transfer would generate chained moves, it will not. The chained | ||
moves need to be released manually. To do so, open "Inventory > Operations > | ||
Stock Moves to Release", select the moves to release and use "action > Release | ||
Stock Move". A move can be released only if the available to promise quantity is | ||
greater than zero. This quantity is computed as the product's virtual quantity | ||
minus the previous moves in the list (previous being defined by the field | ||
"Priority Date"). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from . import test_reservation |
Oops, something went wrong.