Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
stock_available_to_promise_release: qty by date
Browse files Browse the repository at this point in the history
simahawk committed Mar 19, 2020
1 parent 17e22ba commit 6ec3e81
Showing 2 changed files with 98 additions and 7 deletions.
29 changes: 25 additions & 4 deletions stock_available_to_promise_release/models/stock_move.py
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@

from odoo import api, fields, models
from odoo.osv import expression
from odoo.tools import float_compare
from odoo.tools import date_utils, float_compare

from odoo.addons import decimal_precision as dp

@@ -19,7 +19,7 @@ class StockMove(models.Model):
"Used to calculate the ordered available to promise.",
)
ordered_available_to_promise = fields.Float(
"Ordered Available to Promise",
string="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 "
@@ -49,26 +49,47 @@ 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
**self._order_available_to_promise_qty_ctx()
).qty_available
return max(
min(available - self._previous_promised_qty(), self.product_qty), 0.0
)

def _order_available_to_promise_qty_ctx(self):
return {
"location": self.warehouse_id.lot_stock_id.id,
}

def _promise_reservation_horizon(self):
return self.env.company.sudo().stock_reservation_horizon

def _promise_reservation_horizon_date(self):
horizon = self._promise_reservation_horizon()
if horizon:
# start from end of today and add horizon days
return date_utils.add(
date_utils.end_of(fields.Datetime.today(), "day"), days=horizon
)
return None

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),
]
horizon_date = self._promise_reservation_horizon_date()
if horizon_date:
# exclude moves planned beyond the horizon
domain.append(("date_expected", "<", horizon_date))
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(
76 changes: 73 additions & 3 deletions stock_available_to_promise_release/tests/test_reservation.py
Original file line number Diff line number Diff line change
@@ -3,6 +3,9 @@

from datetime import datetime

from dateutil.relativedelta import relativedelta
from freezegun import freeze_time

from odoo import fields
from odoo.tests import common

@@ -28,6 +31,7 @@ def setUpClass(cls):
cls.product2 = cls.env["product.product"].create(
{"name": "Product 2", "type": "product"}
)
cls.uom_unit = cls.env.ref("uom.product_uom_unit")
cls.partner_delta = cls.env.ref("base.res_partner_4")
cls.loc_bin1 = cls.env["stock.location"].create(
{"name": "Bin1", "location_id": cls.loc_stock.id}
@@ -119,6 +123,50 @@ def _deliver(self, picking):
line.qty_done = line.product_qty
picking.action_done()

def test_qty_ctx(self):
move = self.env["stock.move"].create(
{
"name": "testing ctx",
"product_id": self.product1.id,
"product_uom_qty": 1,
"product_uom": self.uom_unit.id,
"warehouse_id": self.wh.id,
"location_id": self.loc_stock.id,
"location_dest_id": self.loc_customer.id,
}
)
ctx = move._order_available_to_promise_qty_ctx()
self.assertEqual(ctx["location"], self.wh.lot_stock_id.id)

def test_horizon_date(self):
move = self.env["stock.move"].create(
{
"name": "testing ctx",
"product_id": self.product1.id,
"product_uom_qty": 1,
"product_uom": self.uom_unit.id,
"warehouse_id": self.wh.id,
"location_id": self.loc_stock.id,
"location_dest_id": self.loc_customer.id,
}
)
from_date = datetime.now().replace(hour=23, minute=59, second=59)
to_date = from_date + relativedelta(days=15)
self.assertEqual(
# skip millisec
move._promise_reservation_horizon_date().timetuple()[:6],
to_date.timetuple()[:6],
)
self.env.company.stock_reservation_horizon = 5
to_date = from_date + relativedelta(days=5)
self.assertEqual(
# skip millisec
move._promise_reservation_horizon_date().timetuple()[:6],
to_date.timetuple()[:6],
)
self.env.company.stock_reservation_horizon = 0
self.assertEqual(move._promise_reservation_horizon_date(), None)

def test_ordered_available_to_promise_value(self):
self.wh.delivery_route_id.write({"available_to_promise_defer_pull": True})
picking = self._out_picking(
@@ -128,20 +176,20 @@ def test_ordered_available_to_promise_value(self):
)
picking2 = self._out_picking(
self._create_picking_chain(
self.wh, [(self.product1, 3)], date=datetime(2019, 9, 2, 16, 1)
self.wh, [(self.product1, 3)], date=datetime(2019, 9, 3, 16, 1)
)
)
# we'll assign this one in the test, should deduct pick 1 and 2
picking3 = self._out_picking(
self._create_picking_chain(
self.wh, [(self.product1, 20)], date=datetime(2019, 9, 3, 16, 0)
self.wh, [(self.product1, 20)], date=datetime(2019, 9, 4, 16, 0)
)
)
# this one should be ignored when we'll assign pick 3 as it has
# a later date
picking4 = self._out_picking(
self._create_picking_chain(
self.wh, [(self.product1, 20)], date=datetime(2019, 9, 4, 16, 1)
self.wh, [(self.product1, 20)], date=datetime(2019, 9, 5, 16, 1)
)
)

@@ -156,6 +204,28 @@ def test_ordered_available_to_promise_value(self):
self.assertEqual(picking3.move_lines._ordered_available_to_promise(), 12)
self.assertEqual(picking4.move_lines._ordered_available_to_promise(), 0)

self.env.company.stock_reservation_horizon = 1
with freeze_time("2019-09-03"):
# set expected date for picking moves far in the future
picking.move_lines.write({"date_expected": "2019-09-10"})
# its quantity won't be counted ib previously reserved
# and we get 3 more on the next one
self.assertEqual(picking3.move_lines._ordered_available_to_promise(), 17)
# do the same for picking 2
picking2.move_lines.write({"date_expected": "2019-09-10"})
# its quantity won't be counted ib previously reserved
# and we get 3 more on the next one
self.assertEqual(picking3.move_lines._ordered_available_to_promise(), 20)
self.assertEqual(picking4.move_lines._ordered_available_to_promise(), 0)
picking3.move_lines.write({"date_expected": "2019-09-10"})
self.assertEqual(picking4.move_lines._ordered_available_to_promise(), 20)

# move the horizon fwd
self.env.company.stock_reservation_horizon = 10
with freeze_time("2019-09-03"):
# last picking won't have available qty again
self.assertEqual(picking4.move_lines._ordered_available_to_promise(), 0)

def test_normal_chain(self):
# usual scenario, without using the option to defer the pull
pickings = self._create_picking_chain(self.wh, [(self.product1, 5)])

0 comments on commit 6ec3e81

Please sign in to comment.